🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / core / data / src / commonMain / kotlin / org / meshtastic / core / data / manager / NodeManagerImpl.kt
Displaying Raw • Download
core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.kt 01cd54907d62cd90913d3d071b6bc240cae365ef (01cd5490) Text, 59.02 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.data.manager
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.atomicfu.atomic
Tff7b72import T7ee787kotlinx.atomicfu.update
Tff7b72import T7ee787kotlinx.collections.immutable.PersistentMap
Tff7b72import T7ee787kotlinx.collections.immutable.PersistentSet
Tff7b72import T7ee787kotlinx.collections.immutable.persistentMapOf
Tff7b72import T7ee787kotlinx.collections.immutable.persistentSetOf
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.sync.Mutex
Tff7b72import T7ee787kotlinx.coroutines.sync.withLock
Tff7b72import T7ee787okio.ByteString
Tff7b72import T7ee787org.koin.core.annotation.Named
Tff7b72import T7ee787org.koin.core.annotation.Single
Tff7b72import T7ee787org.meshtastic.core.common.util.clampTimestampToNow
Tff7b72import T7ee787org.meshtastic.core.common.util.crc32
Tff7b72import T7ee787org.meshtastic.core.common.util.handledLaunch
Tff7b72import T7ee787org.meshtastic.core.model.MyNodeInfo
Tff7b72import T7ee787org.meshtastic.core.model.Node
Tff7b72import T7ee787org.meshtastic.core.model.NodeAddress
Tff7b72import T7ee787org.meshtastic.core.model.util.NodeIdLookup
Tff7b72import T7ee787org.meshtastic.core.repository.ConnectionIdentity
Tff7b72import T7ee787org.meshtastic.core.repository.NodeManager
Tff7b72import T7ee787org.meshtastic.core.repository.NodeRepository
Tff7b72import T7ee787org.meshtastic.core.repository.Notification
Tff7b72import T7ee787org.meshtastic.core.repository.NotificationManager
Tff7b72import T7ee787org.meshtastic.core.repository.RadioInterfaceService
Tff7b72import T7ee787org.meshtastic.core.repository.RadioSessionContext
Tff7b72import T7ee787org.meshtastic.core.resources.Res
Tff7b72import T7ee787org.meshtastic.core.resources.getStringSuspend
Tff7b72import T7ee787org.meshtastic.core.resources.new_node_seen
Tff7b72import T7ee787org.meshtastic.proto.DeviceMetadata
Tff7b72import T7ee787org.meshtastic.proto.FirmwareEdition
Tff7b72import T7ee787org.meshtastic.proto.HardwareModel
Tff7b72import T7ee787org.meshtastic.proto.Paxcount
Tff7b72import T7ee787org.meshtastic.proto.StatusMessage
Tff7b72import T7ee787org.meshtastic.proto.Telemetry
Tff7b72import T7ee787org.meshtastic.proto.User
Tff7b72import T7ee787kotlinx.coroutines.flow.update Tff7b72as Te6edf3updateStateFlow
Tff7b72import T7ee787org.meshtastic.proto.NodeInfo Tff7b72as Te6edf3ProtoNodeInfo
Tff7b72import T7ee787org.meshtastic.proto.Position Tff7b72as Te6edf3ProtoPosition
T8b949e/**
* Resolves a validated public-key correlation hint from a raw [ByteString]. Returns null unless the key is exactly
* [Node.PUBLIC_KEY_SIZE] bytes and is not [Node.ERROR_BYTE_STRING]. The hint is used only for in-memory correlation; it
* is not a trusted hardware identity and never authorizes durable node migration or deletion.
*/
Tff7b72private Tff7b72fun Td2a8ffresolveValidatedPublicKeyHintTb4b4b4(Te6edf3keyTb4b4b4: Te6edf3ByteString?Tb4b4b4)Tb4b4b4: Te6edf3ByteString? Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3key Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3keyTb4b4b4.Te6edf3size Tff7b72!Tff7b72= Te6edf3NodeTb4b4b4.Te6edf3PUBLIC_KEY_SIZE Tff7b72|Tff7b72| Te6edf3key Tff7b72=Tff7b72= Te6edf3NodeTb4b4b4.Te6edf3ERROR_BYTE_STRINGTb4b4b4) Tff7b72return Tff7b72null
Tff7b72return Te6edf3key
Tb4b4b4}
T8b949e/** Single source of truth for stored-node key precedence: the primary field wins over the embedded [User] field. */
Tff7b72private Tff7b72fun Td2a8ffnodePublicKeyHintTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeTb4b4b4)Tb4b4b4: Te6edf3ByteString? Tff7b72=
Te6edf3resolveValidatedPublicKeyHintTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3publicKeyTb4b4b4) Tff7b72?: Te6edf3resolveValidatedPublicKeyHintTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_keyTb4b4b4)
T8b949e/** Short one-way diagnostic value that cannot disclose a raw public-key prefix. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3KEY_FINGERPRINT_HEX_LENGTH Tff7b72= T79c0ff8
Tff7b72internal Tff7b72fun Td2a8ffpublicKeyLogFingerprintTb4b4b4(Te6edf3keyTb4b4b4: Te6edf3ByteStringTb4b4b4)Tb4b4b4: Tffa657String Tff7b72= Te6edf3keyTb4b4b4.Te6edf3sha256Tb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3hexTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3takeTb4b4b4(Te6edf3KEY_FINGERPRINT_HEX_LENGTHTb4b4b4)
Tff7b72private Tff7b72val Te6edf3DEFAULT_NODE_NAME_REGEX Tff7b72= Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff^Meshtastic [0-9a-fA-F]{4}$Ta5d6ff"Tb4b4b4)
T8b949e/** Implementation of [NodeManager] that maintains an in-memory database of the mesh. */
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffLongParameterListTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffLargeClassTa5d6ff"Tb4b4b4)
Tf0883e@SingleTb4b4b4(Te6edf3binds Tff7b72= Tff7b72[Te6edf3NodeManagerTff7b72::Te6edf3classTb4b4b4, Te6edf3NodeIdLookupTff7b72::Te6edf3classTff7b72]Tb4b4b4)
Tff7b72class T56d364NodeManagerImplTb4b4b4(
Tff7b72private Tff7b72val Te6edf3nodeRepositoryTb4b4b4: Te6edf3NodeRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3notificationManagerTb4b4b4: Te6edf3NotificationManagerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3radioInterfaceServiceTb4b4b4: Te6edf3RadioInterfaceServiceTb4b4b4,
Tf0883e@NamedTb4b4b4(Ta5d6ff"Ta5d6ffServiceScopeTa5d6ff"Tb4b4b4) Tff7b72private Tff7b72val Te6edf3scopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3NodeManager Tb4b4b4{
T8b949e// Fixed stripes bound mutex lifetime while preserving same-node persistence ordering. Hash collisions only reduce
T8b949e// parallelism; they never weaken ordering for a node.
Tff7b72private Tff7b72val Te6edf3nodePersistenceLanes Tff7b72= Te6edf3ListTb4b4b4(Te6edf3NODE_PERSISTENCE_LANE_COUNTTb4b4b4) Tb4b4b4{ Te6edf3MutexTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffpersistenceLaneTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3Mutex Tff7b72=
Te6edf3nodePersistenceLanesTff7b72[Tb4b4b4(Te6edf3nodeNumTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Te6edf3and Tffa657IntTb4b4b4.Te6edf3MAX_VALUETb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tff7b72% Te6edf3nodePersistenceLanesTb4b4b4.Te6edf3sizeTff7b72]
T8b949e/**
* Resolves a validated public-key correlation hint from a stored [Node], preferring [Node.publicKey] and falling
* back to [User.public_key] only when the primary field is not itself a valid hint.
*/
Tff7b72internal Tff7b72fun Td2a8ffresolveNodePublicKeyHintTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeTb4b4b4)Tb4b4b4: Te6edf3ByteString? Tff7b72= Te6edf3nodePublicKeyHintTb4b4b4(Te6edf3nodeTb4b4b4)
T8b949e// byNum is the canonical in-memory store. Candidate-number reverse sets make normal put/remove and keyed packet
T8b949e// handling proportional to the affected ID/key candidates rather than the entire mesh. byId stores the selected
T8b949e// deterministic representative for O(1) user-ID lookup.
Tff7b72internal Tff7b72data Tff7b72class T56d364NodeIndexTb4b4b4(
Tff7b72val Te6edf3byNumTb4b4b4: Te6edf3PersistentMapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72> Tff7b72= Te6edf3persistentMapOfTb4b4b4(Tb4b4b4)Tb4b4b4,
Tff7b72val Te6edf3byIdTb4b4b4: Te6edf3PersistentMapTff7b72<Tffa657StringTb4b4b4, Te6edf3NodeTff7b72> Tff7b72= Te6edf3persistentMapOfTb4b4b4(Tb4b4b4)Tb4b4b4,
Tff7b72val Te6edf3candidateNumsByIdTb4b4b4: Te6edf3PersistentMapTff7b72<Tffa657StringTb4b4b4, Te6edf3PersistentSetTff7b72<Tffa657IntTff7b72>Tff7b72> Tff7b72= Te6edf3persistentMapOfTb4b4b4(Tb4b4b4)Tb4b4b4,
Tff7b72val Te6edf3candidateNumsByPublicKeyTb4b4b4: Te6edf3PersistentMapTff7b72<Te6edf3ByteStringTb4b4b4, Te6edf3PersistentSetTff7b72<Tffa657IntTff7b72>Tff7b72> Tff7b72= Te6edf3persistentMapOfTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72fun Td2a8ffputTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4, Te6edf3nodeTb4b4b4: Te6edf3NodeTb4b4b4, Te6edf3preferredNumTb4b4b4: Tffa657Int? Tff7b72= Tff7b72nullTb4b4b4)Tb4b4b4: Te6edf3NodeIndex Tb4b4b4{
Tff7b72val Te6edf3previous Tff7b72= Te6edf3byNumTff7b72[Te6edf3numTff7b72]
Tff7b72val Te6edf3nextByNum Tff7b72= Te6edf3byNumTb4b4b4.Te6edf3puttingTb4b4b4(Te6edf3numTb4b4b4, Te6edf3nodeTb4b4b4)
Tff7b72var Te6edf3nextCandidatesById Tff7b72= Te6edf3candidateNumsById
Tff7b72var Te6edf3nextCandidatesByPublicKey Tff7b72= Te6edf3candidateNumsByPublicKey
Te6edf3previous
Tff7b72?.Te6edf3user
Tff7b72?.Te6edf3id
Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3id Tff7b72-Tff7b72> Te6edf3nextCandidatesById Tff7b72= Te6edf3nextCandidatesByIdTb4b4b4.Te6edf3removingCandidateTb4b4b4(Te6edf3idTb4b4b4, Te6edf3numTb4b4b4) Tb4b4b4}
Te6edf3previous
Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3nodePublicKeyHintTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3key Tff7b72-Tff7b72> Te6edf3nextCandidatesByPublicKey Tff7b72= Te6edf3nextCandidatesByPublicKeyTb4b4b4.Te6edf3removingCandidateTb4b4b4(Te6edf3keyTb4b4b4, Te6edf3numTb4b4b4) Tb4b4b4}
Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id
Tb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3id Tff7b72-Tff7b72> Te6edf3nextCandidatesById Tff7b72= Te6edf3nextCandidatesByIdTb4b4b4.Te6edf3addingCandidateTb4b4b4(Te6edf3idTb4b4b4, Te6edf3numTb4b4b4) Tb4b4b4}
Te6edf3nodePublicKeyHintTb4b4b4(Te6edf3nodeTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3key Tff7b72-Tff7b72>
Te6edf3nextCandidatesByPublicKey Tff7b72= Te6edf3nextCandidatesByPublicKeyTb4b4b4.Te6edf3addingCandidateTb4b4b4(Te6edf3keyTb4b4b4, Te6edf3numTb4b4b4)
Tb4b4b4}
Tff7b72var Te6edf3nextById Tff7b72= Te6edf3byId
Tff7b72val Te6edf3affectedIds Tff7b72= Te6edf3setOfNotNullTb4b4b4(Te6edf3previousTff7b72?.Te6edf3userTff7b72?.Te6edf3idTb4b4b4, Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4)Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72for Tb4b4b4(Te6edf3id Tff7b72in Te6edf3affectedIdsTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3representative Tff7b72= Te6edf3chooseRepresentativeNumTb4b4b4(Te6edf3nextCandidatesByIdTff7b72[Te6edf3idTff7b72]Tb4b4b4.Te6edf3orEmptyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3nextByNumTb4b4b4, Te6edf3preferredNumTb4b4b4)
Te6edf3nextById Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3representative Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3nextByIdTb4b4b4.Te6edf3removingTb4b4b4(Te6edf3idTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3nextByIdTb4b4b4.Te6edf3puttingTb4b4b4(Te6edf3idTb4b4b4, Te6edf3checkNotNullTb4b4b4(Te6edf3nextByNumTff7b72[Te6edf3representativeTff7b72]Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3NodeIndexTb4b4b4(Te6edf3nextByNumTb4b4b4, Te6edf3nextByIdTb4b4b4, Te6edf3nextCandidatesByIdTb4b4b4, Te6edf3nextCandidatesByPublicKeyTb4b4b4)
Tb4b4b4}
T8b949e/**
* Shrinks the index to at most [maxNodes] entries, never removing anything in [keep].
*
* `packet.from` is untrusted and unthrottled, so without this every novel value allocates a permanent [Node]
* and the index grows until the process dies. Only `core:data` reads this index — for correlation,
* notifications, channel lookup and staging DB writes — and every one of those readers already tolerates a
* miss, so dropping a cold entry degrades correlation rather than losing user-visible data. The node list and
* map read Room, not this index, so eviction is not visible in the UI.
*
* Eviction order is least-valuable-first: bare-packet placeholders before nodes that have sent a real NodeInfo,
* and within each group the least recently heard. Nodes the user has marked (favourite, ignored) are never
* evicted, since that is user data rather than observed mesh state.
*
* The cap is therefore best-effort rather than absolute: if protected entries alone exceed [maxNodes] the index
* stays above it. That is deliberate — favourite and ignored are set only by the local user, so no remote party
* can inflate them, and silently discarding user data to satisfy a memory bound would be the worse trade.
*/
Tff7b72fun Td2a8ffevictedToFitTb4b4b4(Te6edf3maxNodesTb4b4b4: Tffa657IntTb4b4b4, Te6edf3keepTb4b4b4: Te6edf3SetTff7b72<Tffa657IntTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3NodeIndex Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3byNumTb4b4b4.Te6edf3size Tff7b72<Tff7b72= Te6edf3maxNodesTb4b4b4) Tff7b72return Tff7b72this
Tff7b72val Te6edf3evictable Tff7b72=
Te6edf3byNumTb4b4b4.Te6edf3values
Tb4b4b4.Te6edf3filterNot Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tff7b72in Te6edf3keep Tff7b72|Tff7b72| Tffa657itTb4b4b4.Te6edf3isFavorite Tff7b72|Tff7b72| Tffa657itTb4b4b4.Te6edf3isIgnored Tb4b4b4}
T8b949e// Placeholders first, then oldest-heard, then node num so the outcome is deterministic.
Tb4b4b4.Te6edf3sortedWithTb4b4b4(
Te6edf3compareByDescendingTff7b72<Te6edf3NodeTff7b72> Tb4b4b4{ Te6edf3isDefaultIdentityPlaceholderTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3thenBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3lastHeard Tb4b4b4}
Tb4b4b4.Te6edf3thenBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3toDrop Tff7b72= Te6edf3evictableTb4b4b4.Te6edf3takeTb4b4b4(Te6edf3byNumTb4b4b4.Te6edf3size Tff7b72- Te6edf3maxNodesTb4b4b4)
Tff7b72return Te6edf3toDropTb4b4b4.Te6edf3foldTb4b4b4(Tff7b72thisTb4b4b4) Tb4b4b4{ Te6edf3indexTb4b4b4, Te6edf3node Tff7b72-Tff7b72> Te6edf3indexTb4b4b4.Te6edf3removeTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Removes [num] from both indices. When the removed node's user ID was the [byId] representative and another
* surviving node shares that ID, [preferredNum] wins when present; otherwise deterministic ordering selects the
* replacement.
*/
Tff7b72fun Td2a8ffremoveTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4, Te6edf3preferredNumTb4b4b4: Tffa657Int? Tff7b72= Tff7b72nullTb4b4b4)Tb4b4b4: Te6edf3NodeIndex Tb4b4b4{
Tff7b72val Te6edf3previous Tff7b72= Te6edf3byNumTff7b72[Te6edf3numTff7b72] Tff7b72?: Tff7b72return Tff7b72this
Tff7b72val Te6edf3newByNum Tff7b72= Te6edf3byNumTb4b4b4.Te6edf3removingTb4b4b4(Te6edf3numTb4b4b4)
Tff7b72var Te6edf3newCandidatesById Tff7b72= Te6edf3candidateNumsById
Tff7b72var Te6edf3newCandidatesByPublicKey Tff7b72= Te6edf3candidateNumsByPublicKey
Te6edf3previousTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id
Tb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3id Tff7b72-Tff7b72> Te6edf3newCandidatesById Tff7b72= Te6edf3newCandidatesByIdTb4b4b4.Te6edf3removingCandidateTb4b4b4(Te6edf3idTb4b4b4, Te6edf3numTb4b4b4) Tb4b4b4}
Te6edf3nodePublicKeyHintTb4b4b4(Te6edf3previousTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3key Tff7b72-Tff7b72>
Te6edf3newCandidatesByPublicKey Tff7b72= Te6edf3newCandidatesByPublicKeyTb4b4b4.Te6edf3removingCandidateTb4b4b4(Te6edf3keyTb4b4b4, Te6edf3numTb4b4b4)
Tb4b4b4}
Tff7b72var Te6edf3newById Tff7b72= Te6edf3byId
Tff7b72if Tb4b4b4(Te6edf3previousTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3survivor Tff7b72=
Te6edf3chooseRepresentativeNumTb4b4b4(Te6edf3newCandidatesByIdTff7b72[Te6edf3previousTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTff7b72]Tb4b4b4.Te6edf3orEmptyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3newByNumTb4b4b4, Te6edf3preferredNumTb4b4b4)
Te6edf3newById Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3survivor Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3newByIdTb4b4b4.Te6edf3puttingTb4b4b4(Te6edf3previousTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4, Te6edf3checkNotNullTb4b4b4(Te6edf3newByNumTff7b72[Te6edf3survivorTff7b72]Tb4b4b4)Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3newByIdTb4b4b4.Te6edf3removingTb4b4b4(Te6edf3previousTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3NodeIndexTb4b4b4(Te6edf3newByNumTb4b4b4, Te6edf3newByIdTb4b4b4, Te6edf3newCandidatesByIdTb4b4b4, Te6edf3newCandidatesByPublicKeyTb4b4b4)
Tb4b4b4}
Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72internal Tff7b72fun Td2a8ffisDefaultIdentityPlaceholderTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3nodePublicKeyHintTb4b4b4(Te6edf3nodeTb4b4b4) Tff7b72=Tff7b72= Tff7b72null Tff7b72&Tff7b72&
Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hw_model Tff7b72=Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSET Tff7b72&Tff7b72&
Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4) Tff7b72&Tff7b72&
Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3DEFAULT_NODE_NAME_REGEXTb4b4b4)
T8b949e/**
* Selects a deterministic representative number from [candidates] sharing one user ID. If [preferredNum] is
* present it wins; otherwise an established key/hardware identity wins, then the lower node number.
*/
Tff7b72internal Tff7b72fun Td2a8ffchooseRepresentativeNumTb4b4b4(
Te6edf3candidatesTb4b4b4: Te6edf3SetTff7b72<Tffa657IntTff7b72>Tb4b4b4,
Te6edf3nodesTb4b4b4: Te6edf3MapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4,
Te6edf3preferredNumTb4b4b4: Tffa657Int?Tb4b4b4,
Tb4b4b4)Tb4b4b4: Tffa657Int? Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3preferredNum Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3preferredNum Tff7b72in Te6edf3candidatesTb4b4b4) Tff7b72return Te6edf3preferredNum
Tff7b72return Te6edf3candidatesTb4b4b4.Te6edf3minWithOrNullTb4b4b4(
Te6edf3compareByDescendingTff7b72<Tffa657IntTff7b72> Tb4b4b4{ Te6edf3num Tff7b72-Tff7b72>
Te6edf3nodesTff7b72[Te6edf3numTff7b72]Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
Te6edf3nodePublicKeyHintTb4b4b4(Te6edf3nodeTb4b4b4) Tff7b72!Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hw_model Tff7b72!Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSET
Tb4b4b4} Tff7b72=Tff7b72= Tff7b72true
Tb4b4b4}
Tb4b4b4.Te6edf3thenBy Tb4b4b4{ Tffa657it Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72fun Td2a8fffromByNumTb4b4b4(Te6edf3nodesTb4b4b4: Te6edf3MapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3NodeIndex Tb4b4b4{
Tff7b72var Te6edf3result Tff7b72= Te6edf3NodeIndexTb4b4b4(Tb4b4b4)
Te6edf3nodesTb4b4b4.Te6edf3forEach Tb4b4b4{ Tb4b4b4(Te6edf3numTb4b4b4, Te6edf3nodeTb4b4b4) Tff7b72-Tff7b72> Te6edf3result Tff7b72= Te6edf3resultTb4b4b4.Te6edf3putTb4b4b4(Te6edf3numTb4b4b4, Te6edf3nodeTb4b4b4) Tb4b4b4}
Tff7b72return Te6edf3result
Tb4b4b4}
Tff7b72private Tff7b72fun Tff7b72<Te6edf3KTff7b72> Td2a8ffPersistentMapTff7b72<Te6edf3KTb4b4b4, Te6edf3PersistentSetTff7b72<Tffa657IntTff7b72>Tff7b72>Tb4b4b4.Te6edf3addingCandidateTb4b4b4(
Te6edf3keyTb4b4b4: Te6edf3KTb4b4b4,
Te6edf3numTb4b4b4: Tffa657IntTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3PersistentMapTff7b72<Te6edf3KTb4b4b4, Te6edf3PersistentSetTff7b72<Tffa657IntTff7b72>Tff7b72> Tff7b72= Te6edf3puttingTb4b4b4(Te6edf3keyTb4b4b4, Tb4b4b4(Tff7b72thisTff7b72[Te6edf3keyTff7b72] Tff7b72?: Te6edf3persistentSetOfTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3addingTb4b4b4(Te6edf3numTb4b4b4)Tb4b4b4)
Tff7b72private Tff7b72fun Tff7b72<Te6edf3KTff7b72> Td2a8ffPersistentMapTff7b72<Te6edf3KTb4b4b4, Te6edf3PersistentSetTff7b72<Tffa657IntTff7b72>Tff7b72>Tb4b4b4.Te6edf3removingCandidateTb4b4b4(
Te6edf3keyTb4b4b4: Te6edf3KTb4b4b4,
Te6edf3numTb4b4b4: Tffa657IntTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3PersistentMapTff7b72<Te6edf3KTb4b4b4, Te6edf3PersistentSetTff7b72<Tffa657IntTff7b72>Tff7b72> Tb4b4b4{
Tff7b72val Te6edf3remaining Tff7b72= Tff7b72thisTff7b72[Te6edf3keyTff7b72]Tff7b72?.Te6edf3removingTb4b4b4(Te6edf3numTb4b4b4) Tff7b72?: Tff7b72return Tff7b72this
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3remainingTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3removingTb4b4b4(Te6edf3keyTb4b4b4) Tff7b72else Te6edf3puttingTb4b4b4(Te6edf3keyTb4b4b4, Te6edf3remainingTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72data Tff7b72class T56d364NodeStateTb4b4b4(
Tff7b72val Te6edf3indexTb4b4b4: Te6edf3NodeIndex Tff7b72= Te6edf3NodeIndexTb4b4b4(Tb4b4b4)Tb4b4b4,
Tff7b72val Te6edf3localNodeNumTb4b4b4: Tffa657Int? Tff7b72= Tff7b72nullTb4b4b4,
Tff7b72val Te6edf3retiredNodeNumsTb4b4b4: Te6edf3PersistentSetTff7b72<Tffa657IntTff7b72> Tff7b72= Te6edf3persistentSetOfTb4b4b4(Tb4b4b4)Tb4b4b4,
T8b949e/**
* Validated public-key hint for each retired number, captured before index removal. Used to suppress same-key
* replays even when no canonical candidate currently holds the key in the in-memory index.
*/
Tff7b72val Te6edf3retiredKeyHintsTb4b4b4: Te6edf3PersistentMapTff7b72<Tffa657IntTb4b4b4, Te6edf3ByteStringTff7b72> Tff7b72= Te6edf3persistentMapOfTb4b4b4(Tb4b4b4)Tb4b4b4,
T8b949e/**
* Monotonic counter bumped on every live mutation to [index] or [localNodeNum]. Captured by [loadCachedNodeDB]
* before its suspending snapshot read so the commit step can detect concurrent live mutations and merge instead
* of overwriting them.
*/
Tff7b72val Te6edf3revisionTb4b4b4: Tffa657Long Tff7b72= T79c0ff0LTb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72fun Td2a8ffisRetiredAbsentTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3nodeNum Tff7b72in Te6edf3retiredNodeNums Tff7b72&Tff7b72& Te6edf3nodeNum Tff7b72!in Te6edf3indexTb4b4b4.Te6edf3byNum
Tb4b4b4}
Tff7b72private Tff7b72val Te6edf3nodeState Tff7b72= Te6edf3atomicTb4b4b4(Te6edf3NodeStateTb4b4b4(Tb4b4b4)Tb4b4b4)
T8b949e/**
* Session generation, bumped by [clear]. Captured by [loadCachedNodeDB] before its suspending snapshot read so a
* load launched in one session can be discarded completely if [clear] started a new session before the load
* committed.
*/
Tff7b72private Tff7b72val Te6edf3sessionGeneration Tff7b72= Te6edf3atomicTb4b4b4(T79c0ff0LTb4b4b4)
Tff7b72override Tff7b72val Te6edf3nodeDBbyNodeNumTb4b4b4: Te6edf3MapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNum
Tff7b72override Tff7b72fun Td2a8ffgetNodeByIdTb4b4b4(Te6edf3idTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3Node? Tff7b72= Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byIdTff7b72[Te6edf3idTff7b72]
Tff7b72override Tff7b72val Te6edf3isNodeDbReady Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Tff7b72falseTb4b4b4)
Tff7b72override Tff7b72val Te6edf3allowNodeDbWrites Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Tff7b72falseTb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffsetNodeDbReadyTb4b4b4(Te6edf3readyTb4b4b4: Tffa657BooleanTb4b4b4) Tb4b4b4{
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Te6edf3ready
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffsetAllowNodeDbWritesTb4b4b4(Te6edf3allowedTb4b4b4: Tffa657BooleanTb4b4b4) Tb4b4b4{
Te6edf3allowNodeDbWritesTb4b4b4.Te6edf3value Tff7b72= Te6edf3allowed
Tb4b4b4}
Tff7b72override Tff7b72val Te6edf3myNodeNum Tff7b72= Te6edf3MutableStateFlowTff7b72<Tffa657Int?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffsetMyNodeNumTb4b4b4(Te6edf3numTb4b4b4: Tffa657Int?Tb4b4b4) Tb4b4b4{
Te6edf3nodeStateTb4b4b4.Te6edf3update Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3localNodeNum Tff7b72= Te6edf3numTb4b4b4, Te6edf3revision Tff7b72= Tffa657itTb4b4b4.Te6edf3revision Tff7b72+ T79c0ff1Tb4b4b4) Tb4b4b4}
Te6edf3myNodeNumTb4b4b4.Te6edf3value Tff7b72= Te6edf3num
Tb4b4b4}
Tff7b72override Tff7b72val Te6edf3myDeviceId Tff7b72= Te6edf3MutableStateFlowTff7b72<Tffa657String?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffsetMyDeviceIdTb4b4b4(Te6edf3idTb4b4b4: Tffa657String?Tb4b4b4) Tb4b4b4{
Te6edf3myDeviceIdTb4b4b4.Te6edf3value Tff7b72= Te6edf3id
Tb4b4b4}
Tff7b72private Tff7b72val Te6edf3_connectionIdentity Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3ConnectionIdentity?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72override Tff7b72val Te6edf3connectionIdentityTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3ConnectionIdentity?Tff7b72> Tff7b72= Te6edf3_connectionIdentity
Tff7b72override Tff7b72fun Td2a8ffclearConnectionIdentityTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3_connectionIdentityTb4b4b4.Te6edf3value Tff7b72= Tff7b72null
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffclearStaleConnectionIdentityTb4b4b4(Te6edf3activeSessionGenerationTb4b4b4: Tffa657LongTb4b4b4) Tb4b4b4{
Te6edf3_connectionIdentityTb4b4b4.Te6edf3updateStateFlow Tb4b4b4{ Te6edf3identity Tff7b72-Tff7b72>
Te6edf3identityTff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3sessionGeneration Tff7b72=Tff7b72= Te6edf3activeSessionGeneration Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffpublishConnectionIdentityTb4b4b4(Te6edf3sessionGenerationTb4b4b4: Tffa657LongTb4b4b4, Te6edf3addressTb4b4b4: Tffa657StringTb4b4b4, Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3deviceIdTb4b4b4: Tffa657String?Tb4b4b4) Tb4b4b4{
Te6edf3_connectionIdentityTb4b4b4.Te6edf3value Tff7b72= Te6edf3ConnectionIdentityTb4b4b4(Te6edf3sessionGenerationTb4b4b4, Te6edf3addressTb4b4b4, Te6edf3nodeNumTb4b4b4, Te6edf3deviceIdTb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72val Te6edf3firmwareEdition Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3FirmwareEdition?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffsetFirmwareEditionTb4b4b4(Te6edf3editionTb4b4b4: Te6edf3FirmwareEdition?Tb4b4b4) Tb4b4b4{
Te6edf3firmwareEditionTb4b4b4.Te6edf3value Tff7b72= Te6edf3edition
Tb4b4b4}
Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72const Tff7b72val Te6edf3NODE_PERSISTENCE_LANE_COUNT Tff7b72= T79c0ff6T79c0ff4
Tff7b72private Tff7b72const Tff7b72val Te6edf3TIME_MS_TO_S Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0L
Tff7b72private Tff7b72const Tff7b72val Te6edf3GENERATED_NODE_NAME_SUFFIX_LENGTH Tff7b72= T79c0ff4
T8b949e/** `precision_bits` value used by firmware for an un-degraded (full 32-bit) coordinate. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3FULL_POSITION_PRECISION_BITS Tff7b72= T79c0ff3T79c0ff2
T8b949e/** Total bits in the `latitude_i` / `longitude_i` fixed-point representation. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3POSITION_COORDINATE_BITS Tff7b72= T79c0ff3T79c0ff2
T8b949e/**
* Reproduces the firmware's coarsening of a coordinate to [bits] of precision: mask off the low bits, then
* re-center the value inside the remaining box. Mirrors `Router::sendLocal` / `PositionModule` behaviour so we
* can tell whether a coarse report describes the box that already contains a coordinate we hold.
*/
Tff7b72internal Tff7b72fun Td2a8ffcoarsenCoordinateTb4b4b4(Te6edf3degITb4b4b4: Tffa657IntTb4b4b4, Te6edf3bitsTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Int Tff7b72=
Tb4b4b4(Te6edf3degI Te6edf3and Tb4b4b4(Tff7b72-T79c0ff1 Te6edf3shl Tb4b4b4(Te6edf3POSITION_COORDINATE_BITS Tff7b72- Te6edf3bitsTb4b4b4)Tb4b4b4)Tb4b4b4) Tff7b72+ Tb4b4b4(T79c0ff1 Te6edf3shl Tb4b4b4(Te6edf3POSITION_COORDINATE_BITS Tff7b72- T79c0ff1 Tff7b72- Te6edf3bitsTb4b4b4)Tb4b4b4)
T8b949e/**
* Returns [incoming] with the coordinates of [stored] restored when [incoming] is merely a precision-degraded
* restatement of what we already know (see [isRedundantCoarsePosition]); otherwise [incoming] unchanged.
*/
Tff7b72internal Tff7b72fun Td2a8ffpreservingKnownPrecisionTb4b4b4(Te6edf3incomingTb4b4b4: Te6edf3ProtoPositionTb4b4b4, Te6edf3storedTb4b4b4: Te6edf3ProtoPositionTb4b4b4)Tb4b4b4: Te6edf3ProtoPosition Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3isRedundantCoarsePositionTb4b4b4(Te6edf3incomingTb4b4b4, Te6edf3storedTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3incomingTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3latitude_i Tff7b72= Te6edf3storedTb4b4b4.Te6edf3latitude_iTb4b4b4,
Te6edf3longitude_i Tff7b72= Te6edf3storedTb4b4b4.Te6edf3longitude_iTb4b4b4,
Te6edf3precision_bits Tff7b72= Te6edf3storedTb4b4b4.Te6edf3precision_bitsTb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3incoming
Tb4b4b4}
T8b949e/**
* True when [incoming] is a precision-degraded report that carries no new information over [stored].
*
* Nodes on a channel with `position_precision` set broadcast their location coarsened to the box centre. When
* we already hold a more precise coordinate for that node (e.g. a fixed position we just wrote, or an earlier
* full-precision report) and that coordinate falls inside the same box, adopting the coarse value would make
* the position visibly "drift" away from the real one (issue #6360). Only positions from a *different* box
* indicate the node actually moved, and those are accepted normally.
*/
Tff7b72internal Tff7b72fun Td2a8ffisRedundantCoarsePositionTb4b4b4(Te6edf3incomingTb4b4b4: Te6edf3ProtoPositionTb4b4b4, Te6edf3storedTb4b4b4: Te6edf3ProtoPositionTb4b4b4)Tb4b4b4: Tffa657Boolean Tb4b4b4{
Tff7b72val Te6edf3incomingBits Tff7b72= Te6edf3incomingTb4b4b4.Te6edf3precision_bits
Tff7b72val Te6edf3storedLatI Tff7b72= Te6edf3storedTb4b4b4.Te6edf3latitude_i Tff7b72?: T79c0ff0
Tff7b72val Te6edf3storedLonI Tff7b72= Te6edf3storedTb4b4b4.Te6edf3longitude_i Tff7b72?: T79c0ff0
T8b949e// `0` means the stored coordinate was never degraded, so treat it as full precision.
Tff7b72val Te6edf3storedBits Tff7b72= Te6edf3storedTb4b4b4.Te6edf3precision_bitsTb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72> T79c0ff0 Tb4b4b4} Tff7b72?: Te6edf3FULL_POSITION_PRECISION_BITS
Tff7b72val Te6edf3isDegradedReport Tff7b72= Te6edf3incomingBits Tff7b72> T79c0ff0 Tff7b72&Tff7b72& Te6edf3incomingBits Tff7b72< Te6edf3FULL_POSITION_PRECISION_BITS
Tff7b72val Te6edf3haveMorePreciseCoordinate Tff7b72= Tb4b4b4(Te6edf3storedLatI Tff7b72!Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Te6edf3storedLonI Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tff7b72&Tff7b72& Te6edf3storedBits Tff7b72> Te6edf3incomingBits
Tff7b72return Te6edf3isDegradedReport Tff7b72&Tff7b72&
Te6edf3haveMorePreciseCoordinate Tff7b72&Tff7b72&
Te6edf3coarsenCoordinateTb4b4b4(Te6edf3storedLatITb4b4b4, Te6edf3incomingBitsTb4b4b4) Tff7b72=Tff7b72= Tb4b4b4(Te6edf3incomingTb4b4b4.Te6edf3latitude_i Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72&Tff7b72&
Te6edf3coarsenCoordinateTb4b4b4(Te6edf3storedLonITb4b4b4, Te6edf3incomingBitsTb4b4b4) Tff7b72=Tff7b72= Tb4b4b4(Te6edf3incomingTb4b4b4.Te6edf3longitude_i Tff7b72?: T79c0ff0Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Ceiling on entries in the in-memory node index. See [NodeIndex.evictedToFit].
*
* An order of magnitude above the largest NodeDB firmware exposes (`MAX_NUM_NODES` is 100–250 by target), so a
* legitimately busy mesh never reaches it and only sustained novel-`from` traffic does.
*/
Tff7b72const Tff7b72val Te6edf3MAX_IN_MEMORY_NODES Tff7b72= T79c0ff2Te6edf3_000
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffloadCachedNodeDBTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// NOTE: this method intentionally does NOT touch _connectionIdentity. The connection-session identity is only
T8b949e// populated by a fresh MyNodeInfo handshake; restoring it from cache would re-introduce a stale-identity
T8b949e// association across transport switches.
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
T8b949e// Capture the session generation and live revision BEFORE the suspending DB read. After the read returns
T8b949e// we re-check both: a generation mismatch means clear() started a new session while we were reading, so
T8b949e// the result is discarded completely; a revision mismatch means a live packet mutated the index while we
T8b949e// were reading, so we merge instead of overwriting (otherwise a node received during the load window would
T8b949e// be silently dropped).
Tff7b72val Te6edf3generation Tff7b72= Te6edf3sessionGenerationTb4b4b4.Te6edf3value
Tff7b72val Te6edf3revisionAtStart Tff7b72= Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3revision
T8b949e// Read from the CURRENTLY SELECTED database through DatabaseProvider.withReadDb, not from the
T8b949e// process-wide nodeDBbyNum StateFlow. The StateFlow is a stateIn cache over SharingStarted.Eagerly and
T8b949e// can briefly retain the PREVIOUS database's map after a currentDb switch, which would resurrect retired
T8b949e// or stale rows on the new session.
Tff7b72val Te6edf3snapshot Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3getNodeDbSnapshotTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3persistedLocalNum Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfoTb4b4b4.Te6edf3valueTff7b72?.Te6edf3myNodeNum
Te6edf3nodeStateTb4b4b4.Te6edf3update Tb4b4b4{ Te6edf3state Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3generation Tff7b72!Tff7b72= Te6edf3sessionGenerationTb4b4b4.Te6edf3valueTb4b4b4) Tb4b4b4{
T8b949e// Session was reset while the load was in flight; drop the result entirely.
Te6edf3state
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72val Te6edf3retiredNums Tff7b72= Te6edf3stateTb4b4b4.Te6edf3retiredNodeNums
T8b949e// Retirement always wins: rows for currently retired numbers never re-enter the live index from
T8b949e// the snapshot, even if the cache still holds them.
Tff7b72val Te6edf3filteredSnapshot Tff7b72= Te6edf3snapshotTb4b4b4.Te6edf3filterKeys Tb4b4b4{ Tffa657it Tff7b72!in Te6edf3retiredNums Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3stateTb4b4b4.Te6edf3revision Tff7b72=Tff7b72= Te6edf3revisionAtStartTb4b4b4) Tb4b4b4{
T8b949e// No live mutation since capture: install the filtered snapshot directly. Preserve any
T8b949e// local-node number learned during the session; fall back to the persisted value only when
T8b949e// we have none yet.
Te6edf3state
Tb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3index Tff7b72= Te6edf3NodeIndexTb4b4b4.Te6edf3fromByNumTb4b4b4(Te6edf3filteredSnapshotTb4b4b4)Tb4b4b4,
Te6edf3localNodeNum Tff7b72= Te6edf3stateTb4b4b4.Te6edf3localNodeNum Tff7b72?: Te6edf3persistedLocalNumTb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3withBoundedIndexTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Live state changed during the load window. Merge: current nodes (with their fresher
T8b949e// fields) win over snapshot rows at the same num; snapshot rows only fill slots that the
T8b949e// live index does not already carry. Live local-node info and retirement state remain
T8b949e// authoritative; a missing local number receives the same persisted fallback as a direct load.
Tff7b72val Te6edf3liveByNum Tff7b72= Te6edf3stateTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNum
Tff7b72val Te6edf3merged Tff7b72= Te6edf3filteredSnapshotTb4b4b4.Te6edf3toMutableMapTb4b4b4(Tb4b4b4)
Te6edf3liveByNumTb4b4b4.Te6edf3forEach Tb4b4b4{ Tb4b4b4(Te6edf3numTb4b4b4, Te6edf3nodeTb4b4b4) Tff7b72-Tff7b72> Te6edf3mergedTff7b72[Te6edf3numTff7b72] Tff7b72= Te6edf3node Tb4b4b4}
Te6edf3state
Tb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3index Tff7b72= Te6edf3NodeIndexTb4b4b4.Te6edf3fromByNumTb4b4b4(Te6edf3mergedTb4b4b4)Tb4b4b4,
Te6edf3localNodeNum Tff7b72= Te6edf3stateTb4b4b4.Te6edf3localNodeNum Tff7b72?: Te6edf3persistedLocalNumTb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3withBoundedIndexTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// Keep the published myNodeNum consistent with whatever state survived the commit above. If the load was
T8b949e// discarded, the published value is whatever clear()/setMyNodeNum() left behind.
Te6edf3myNodeNumTb4b4b4.Te6edf3value Tff7b72= Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3localNodeNum
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffclearTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Bump the session generation FIRST so any in-flight loadCachedNodeDB whose suspend read returns after this
T8b949e// point sees a mismatch and discards its result instead of installing stale state into the freshly cleared
T8b949e// index.
Te6edf3sessionGenerationTb4b4b4.Te6edf3incrementAndGetTb4b4b4(Tb4b4b4)
Te6edf3nodeStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3NodeStateTb4b4b4(Tb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Te6edf3allowNodeDbWritesTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Te6edf3myNodeNumTb4b4b4.Te6edf3value Tff7b72= Tff7b72null
Te6edf3myDeviceIdTb4b4b4.Te6edf3value Tff7b72= Tff7b72null
Te6edf3firmwareEditionTb4b4b4.Te6edf3value Tff7b72= Tff7b72null
Te6edf3_connectionIdentityTb4b4b4.Te6edf3value Tff7b72= Tff7b72null
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffgetMyNodeInfoTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3MyNodeInfo? Tb4b4b4{
Tff7b72val Te6edf3mi Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfoTb4b4b4.Te6edf3value Tff7b72?: Tff7b72return Tff7b72null
Tff7b72val Te6edf3myNode Tff7b72= Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNumTff7b72[Te6edf3miTb4b4b4.Te6edf3myNodeNumTff7b72]
Tff7b72return Te6edf3MyNodeInfoTb4b4b4(
Te6edf3myNodeNum Tff7b72= Te6edf3miTb4b4b4.Te6edf3myNodeNumTb4b4b4,
Te6edf3hasGPS Tff7b72= Tb4b4b4(Te6edf3myNodeTff7b72?.Te6edf3positionTff7b72?.Te6edf3latitude_i Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72!Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3model Tff7b72= Te6edf3miTb4b4b4.Te6edf3model Tff7b72?: Te6edf3myNodeTff7b72?.Te6edf3userTff7b72?.Te6edf3hw_modelTff7b72?.Te6edf3nameTb4b4b4,
Te6edf3firmwareVersion Tff7b72= Te6edf3miTb4b4b4.Te6edf3firmwareVersionTb4b4b4,
Te6edf3couldUpdate Tff7b72= Te6edf3miTb4b4b4.Te6edf3couldUpdateTb4b4b4,
Te6edf3shouldUpdate Tff7b72= Te6edf3miTb4b4b4.Te6edf3shouldUpdateTb4b4b4,
Te6edf3currentPacketId Tff7b72= Te6edf3miTb4b4b4.Te6edf3currentPacketIdTb4b4b4,
Te6edf3messageTimeoutMsec Tff7b72= Te6edf3miTb4b4b4.Te6edf3messageTimeoutMsecTb4b4b4,
Te6edf3minAppVersion Tff7b72= Te6edf3miTb4b4b4.Te6edf3minAppVersionTb4b4b4,
Te6edf3maxChannels Tff7b72= Te6edf3miTb4b4b4.Te6edf3maxChannelsTb4b4b4,
Te6edf3hasWifi Tff7b72= Te6edf3miTb4b4b4.Te6edf3hasWifiTb4b4b4,
Te6edf3channelUtilization Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3airUtilTx Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3deviceId Tff7b72= Te6edf3miTb4b4b4.Te6edf3deviceId Tff7b72?: Te6edf3myNodeTff7b72?.Te6edf3userTff7b72?.Te6edf3idTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffgetMyIdTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657String Tb4b4b4{
Tff7b72val Te6edf3num Tff7b72= Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3localNodeNum Tff7b72?: Te6edf3nodeRepositoryTb4b4b4.Te6edf3myNodeInfoTb4b4b4.Te6edf3valueTff7b72?.Te6edf3myNodeNum Tff7b72?: Tff7b72return Ta5d6ff"Ta5d6ff"
Tff7b72return Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNumTff7b72[Te6edf3numTff7b72]Tff7b72?.Te6edf3userTff7b72?.Te6edf3id Tff7b72?: Ta5d6ff"Ta5d6ff"
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffremoveByNodenumTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Te6edf3nodeStateTb4b4b4.Te6edf3update Tb4b4b4{ Te6edf3state Tff7b72-Tff7b72> Te6edf3stateTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3index Tff7b72= Te6edf3stateTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3removeTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4, Te6edf3revision Tff7b72= Te6edf3stateTb4b4b4.Te6edf3revision Tff7b72+ T79c0ff1Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffapplyTrustedIdentityMigrationsTb4b4b4(Te6edf3removedNumsTb4b4b4: Te6edf3CollectionTff7b72<Tffa657IntTff7b72>Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3removedNumsTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return
Tff7b72var Te6edf3committedHintsTb4b4b4: Te6edf3MapTff7b72<Tffa657IntTb4b4b4, Te6edf3ByteString?Tff7b72> Tff7b72= Te6edf3emptyMapTb4b4b4(Tb4b4b4)
Tff7b72var Te6edf3committedPresentNumsTb4b4b4: Te6edf3SetTff7b72<Tffa657IntTff7b72> Tff7b72= Te6edf3emptySetTb4b4b4(Tb4b4b4)
Te6edf3nodeStateTb4b4b4.Te6edf3update Tb4b4b4{ Te6edf3state Tff7b72-Tff7b72>
T8b949e// Capture validated public-key hints from the current index state before removal. A number that is already
T8b949e// absent (e.g. re-applying the same migration after a previous retirement) has no current node to capture
T8b949e// a key from — its previously captured hint MUST be preserved so same-key replay stays suppressed and a
T8b949e// later genuinely different-keyed device can still claim the slot under the intended contract.
Te6edf3committedPresentNums Tff7b72= Te6edf3removedNumsTb4b4b4.Te6edf3filterToTb4b4b4(Te6edf3mutableSetOfTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{ Tffa657it Tff7b72in Te6edf3stateTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNum Tb4b4b4}
Te6edf3committedHints Tff7b72=
Te6edf3removedNumsTb4b4b4.Te6edf3associateWith Tb4b4b4{ Te6edf3num Tff7b72-Tff7b72>
Te6edf3stateTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNumTff7b72[Te6edf3numTff7b72]Tff7b72?.Te6edf3letTb4b4b4(Tff7b72::Te6edf3resolveNodePublicKeyHintTb4b4b4) Tff7b72?: Te6edf3stateTb4b4b4.Te6edf3retiredKeyHintsTff7b72[Te6edf3numTff7b72]
Tb4b4b4}
Te6edf3stateTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3index Tff7b72= Te6edf3removedNumsTb4b4b4.Te6edf3foldTb4b4b4(Te6edf3stateTb4b4b4.Te6edf3indexTb4b4b4) Tb4b4b4{ Te6edf3indexTb4b4b4, Te6edf3nodeNum Tff7b72-Tff7b72> Te6edf3indexTb4b4b4.Te6edf3removeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3retiredNodeNums Tff7b72= Te6edf3stateTb4b4b4.Te6edf3retiredNodeNumsTb4b4b4.Te6edf3addingAllTb4b4b4(Te6edf3removedNumsTb4b4b4)Tb4b4b4,
T8b949e// Only ADD hints here; never remove a prior hint for an absent number. A subsequent migration that
T8b949e// re-applies the same retirement must preserve the original hint so same-key replay stays suppressed
T8b949e// and a later genuinely different-keyed device can still claim the slot.
Te6edf3retiredKeyHints Tff7b72=
Te6edf3stateTb4b4b4.Te6edf3retiredKeyHintsTb4b4b4.Te6edf3puttingAllTb4b4b4(
Te6edf3committedHintsTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Tb4b4b4(Te6edf3numTb4b4b4, Te6edf3keyTb4b4b4) Tff7b72-Tff7b72> Te6edf3keyTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3num Te6edf3to Tffa657it Tb4b4b4} Tb4b4b4}Tb4b4b4.Te6edf3toMapTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3revision Tff7b72= Te6edf3stateTb4b4b4.Te6edf3revision Tff7b72+ T79c0ff1Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// Commit retirement first. A dispatch already in progress will fail its final state revalidation; one that
T8b949e// completed before the commit is removed by this cancellation. Side effects run once, outside the CAS loop.
Te6edf3removedNumsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3num Tff7b72-Tff7b72>
Te6edf3notificationManagerTb4b4b4.Te6edf3cancelTb4b4b4(Te6edf3numTb4b4b4)
Tff7b72val Te6edf3keyDescription Tff7b72=
Te6edf3committedHintsTff7b72[Te6edf3numTff7b72]Tff7b72?.Te6edf3letTb4b4b4(Tff7b72::Te6edf3publicKeyLogFingerprintTb4b4b4)
Tff7b72?: Tff7b72if Tb4b4b4(Te6edf3num Tff7b72in Te6edf3committedPresentNumsTb4b4b4) Ta5d6ff"Ta5d6ffnoneTa5d6ff" Tff7b72else Ta5d6ff"Ta5d6ffabsentTa5d6ff"
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{
Ta5d6ff"Ta5d6ff[NodeIdentity] trusted-migration removed=Tffd700$Te6edf3numTa5d6ff" Tff7b72+ Ta5d6ff"Ta5d6ff key=Tffd700$Te6edf3keyDescriptionTa5d6ff notificationCancelled=trueTa5d6ff"
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72internal Tff7b72fun Td2a8ffgetOrCreateNodeTb4b4b4(Te6edf3nTb4b4b4: Tffa657IntTb4b4b4, Te6edf3channelTb4b4b4: Tffa657Int Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4: Te6edf3Node Tff7b72=
Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNumTff7b72[Te6edf3nTff7b72] Tff7b72?: Te6edf3createDefaultNodeTb4b4b4(Te6edf3nTb4b4b4, Te6edf3channelTb4b4b4)
T8b949e/**
* Applies [MAX_IN_MEMORY_NODES] to this state's index, protecting the local node and [alsoKeep].
*
* EVERY path that installs an index must go through this. There are three — the ordinary [updateNodeState] reducer,
* the [handleReceivedUser] commit, and the [loadCachedNodeDB] snapshot install — and bounding only the first left
* the NodeInfo path, which is the one an unauthenticated peer drives most directly, completely unbounded.
*
* Returns the receiver unchanged when nothing needs evicting, so a CAS on the result stays cheap.
*/
Tff7b72private Tff7b72fun Te6edf3NodeStateTb4b4b4.Td2a8ffwithBoundedIndexTb4b4b4(Te6edf3alsoKeepTb4b4b4: Tffa657Int? Tff7b72= Tff7b72nullTb4b4b4)Tb4b4b4: Te6edf3NodeState Tb4b4b4{
Tff7b72val Te6edf3bounded Tff7b72= Te6edf3indexTb4b4b4.Te6edf3evictedToFitTb4b4b4(Te6edf3MAX_IN_MEMORY_NODESTb4b4b4, Te6edf3keep Tff7b72= Te6edf3setOfNotNullTb4b4b4(Te6edf3alsoKeepTb4b4b4, Te6edf3localNodeNumTb4b4b4)Tb4b4b4)
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3bounded Tff7b72=Tff7b72=Tff7b72= Te6edf3indexTb4b4b4) Tff7b72this Tff7b72else Te6edf3copyTb4b4b4(Te6edf3index Tff7b72= Te6edf3boundedTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72data Tff7b72class T56d364NodeStateChangeTb4b4b4(Tff7b72val Te6edf3previousTb4b4b4: Te6edf3NodeTb4b4b4, Tff7b72val Te6edf3nextTb4b4b4: Te6edf3NodeTb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffupdateNodeStateTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3channelTb4b4b4: Tffa657IntTb4b4b4, Te6edf3transformTb4b4b4: Tb4b4b4(Te6edf3NodeTb4b4b4) Tff7b72-Tff7b72> Te6edf3NodeTb4b4b4)Tb4b4b4: Te6edf3NodeStateChange? Tb4b4b4{
Tff7b72var Te6edf3changeTb4b4b4: Te6edf3NodeStateChange? Tff7b72= Tff7b72null
Te6edf3nodeStateTb4b4b4.Te6edf3update Tb4b4b4{ Te6edf3state Tff7b72-Tff7b72>
Te6edf3change Tff7b72= Tff7b72null
Tff7b72if Tb4b4b4(Te6edf3stateTb4b4b4.Te6edf3isRetiredAbsentTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4) Tff7b72returnTf0883e@update Te6edf3state
Tff7b72val Te6edf3current Tff7b72= Te6edf3stateTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNumTff7b72[Te6edf3nodeNumTff7b72] Tff7b72?: Te6edf3createDefaultNodeTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3channelTb4b4b4)
Tff7b72val Te6edf3next Tff7b72= Te6edf3transformTb4b4b4(Te6edf3currentTb4b4b4)
Te6edf3change Tff7b72= Te6edf3NodeStateChangeTb4b4b4(Te6edf3previous Tff7b72= Te6edf3currentTb4b4b4, Te6edf3next Tff7b72= Te6edf3nextTb4b4b4)
Te6edf3state
Tb4b4b4.Te6edf3copyTb4b4b4(Te6edf3index Tff7b72= Te6edf3stateTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3putTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3nextTb4b4b4)Tb4b4b4, Te6edf3revision Tff7b72= Te6edf3stateTb4b4b4.Te6edf3revision Tff7b72+ T79c0ff1Tb4b4b4)
Tb4b4b4.Te6edf3withBoundedIndexTb4b4b4(Te6edf3alsoKeep Tff7b72= Te6edf3nodeNumTb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3change
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffshouldPersistTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72=
Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72& Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72&Tff7b72& Te6edf3allowNodeDbWritesTb4b4b4.Te6edf3value
Tff7b72private Tff7b72fun Td2a8ffupdateNodeAndSchedulePersistenceTb4b4b4(
Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3channelTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContext? Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3transformTb4b4b4: Tb4b4b4(Te6edf3NodeTb4b4b4) Tff7b72-Tff7b72> Te6edf3NodeTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3NodeStateChange? Tff7b72= Te6edf3updateNodeStateTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3channelTb4b4b4, Te6edf3transformTb4b4b4)Tb4b4b4.Te6edf3also Tb4b4b4{ Te6edf3change Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3change Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3shouldPersistTb4b4b4(Te6edf3changeTb4b4b4.Te6edf3nextTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3launchSessionWorkTb4b4b4(Te6edf3scopeTb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4{ Te6edf3persistLatestNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffupdateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3channelTb4b4b4: Tffa657IntTb4b4b4, Te6edf3transformTb4b4b4: Tb4b4b4(Te6edf3NodeTb4b4b4) Tff7b72-Tff7b72> Te6edf3NodeTb4b4b4) Tb4b4b4{
Te6edf3updateNodeAndSchedulePersistenceTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3channelTb4b4b4, Te6edf3transform Tff7b72= Te6edf3transformTb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffupdateNodeForSessionTb4b4b4(
Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContextTb4b4b4,
Te6edf3channelTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3transformTb4b4b4: Tb4b4b4(Te6edf3NodeTb4b4b4) Tff7b72-Tff7b72> Te6edf3NodeTb4b4b4,
Tb4b4b4) Tb4b4b4{
Te6edf3updateNodeAndSchedulePersistenceTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3channelTb4b4b4, Te6edf3sessionTb4b4b4, Te6edf3transformTb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffupdateNodeAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3channelTb4b4b4: Tffa657IntTb4b4b4, Te6edf3transformTb4b4b4: Tb4b4b4(Te6edf3NodeTb4b4b4) Tff7b72-Tff7b72> Te6edf3NodeTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3result Tff7b72= Te6edf3updateNodeStateTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3channelTb4b4b4, Te6edf3transformTb4b4b4)Tff7b72?.Te6edf3next Tff7b72?: Tff7b72return
Tff7b72if Tb4b4b4(Te6edf3shouldPersistTb4b4b4(Te6edf3resultTb4b4b4)Tb4b4b4) Te6edf3persistLatestNodeTb4b4b4(Te6edf3nodeNumTb4b4b4)
Tb4b4b4}
T8b949e/** Serializes persistence per node and reads the latest in-memory value inside that lane. */
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffpersistLatestNodeTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Te6edf3persistenceLaneTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4.Te6edf3withLock Tb4b4b4{
Tff7b72val Te6edf3latest Tff7b72= Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNumTff7b72[Te6edf3nodeNumTff7b72] Tff7b72?: Tff7b72returnTf0883e@withLock
Tff7b72if Tb4b4b4(Te6edf3shouldPersistTb4b4b4(Te6edf3latestTb4b4b4)Tb4b4b4) Te6edf3nodeRepositoryTb4b4b4.Te6edf3upsertTb4b4b4(Te6edf3latestTb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffhandleReceivedUserTb4b4b4(
Te6edf3fromNumTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3pTb4b4b4: Te6edf3UserTb4b4b4,
Te6edf3channelTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3manuallyVerifiedTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContext?Tb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72while Tb4b4b4(Tff7b72trueTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3before Tff7b72= Te6edf3nodeStateTb4b4b4.Te6edf3value
Tff7b72val Te6edf3transition Tff7b72=
Te6edf3reduceReceivedUserTb4b4b4(
Te6edf3beforeTb4b4b4.Te6edf3indexTb4b4b4,
Te6edf3fromNumTb4b4b4,
Te6edf3pTb4b4b4,
Te6edf3channelTb4b4b4,
Te6edf3manuallyVerifiedTb4b4b4,
Te6edf3beforeTb4b4b4.Te6edf3localNodeNumTb4b4b4,
Te6edf3beforeTb4b4b4.Te6edf3isRetiredAbsentTb4b4b4(Te6edf3fromNumTb4b4b4)Tb4b4b4,
Te6edf3retiredKeyHint Tff7b72= Te6edf3beforeTb4b4b4.Te6edf3retiredKeyHintsTff7b72[Te6edf3fromNumTff7b72]Tb4b4b4,
Tb4b4b4)
Te6edf3receivedUserReductionHookTff7b72?.Te6edf3invokeTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3after Tff7b72=
Te6edf3before
Tb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3index Tff7b72= Te6edf3transitionTb4b4b4.Te6edf3afterTb4b4b4,
Te6edf3retiredNodeNums Tff7b72=
Te6edf3transitionTb4b4b4.Te6edf3unretireNodeNumTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3beforeTb4b4b4.Te6edf3retiredNodeNumsTb4b4b4.Te6edf3removingTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tff7b72?: Te6edf3beforeTb4b4b4.Te6edf3retiredNodeNumsTb4b4b4,
Te6edf3retiredKeyHints Tff7b72=
Te6edf3transitionTb4b4b4.Te6edf3unretireNodeNumTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3beforeTb4b4b4.Te6edf3retiredKeyHintsTb4b4b4.Te6edf3removingTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tff7b72?: Te6edf3beforeTb4b4b4.Te6edf3retiredKeyHintsTb4b4b4,
Te6edf3revision Tff7b72= Te6edf3beforeTb4b4b4.Te6edf3revision Tff7b72+ T79c0ff1Tb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3withBoundedIndexTb4b4b4(Te6edf3alsoKeep Tff7b72= Te6edf3fromNumTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3nodeStateTb4b4b4.Te6edf3compareAndSetTb4b4b4(Te6edf3beforeTb4b4b4, Te6edf3afterTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{
Tff7b72val Te6edf3keyStr Tff7b72= Te6edf3resolveValidatedPublicKeyHintTb4b4b4(Te6edf3pTb4b4b4.Te6edf3public_keyTb4b4b4)Tff7b72?.Te6edf3letTb4b4b4(Tff7b72::Te6edf3publicKeyLogFingerprintTb4b4b4) Tff7b72?: Ta5d6ff"Ta5d6ffnoneTa5d6ff"
Tff7b72val Te6edf3canonicalNum Tff7b72= Te6edf3resolveValidatedPublicKeyHintTb4b4b4(Te6edf3pTb4b4b4.Te6edf3public_keyTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3crc32Tb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Tb4b4b4}
Ta5d6ff"Ta5d6ff[NodeIdentity] user from=Tffd700$Te6edf3fromNumTa5d6ff local=Tffd700${Te6edf3beforeTb4b4b4.Te6edf3localNodeNumTffd700}Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff retired=Tffd700${Te6edf3beforeTb4b4b4.Te6edf3isRetiredAbsentTb4b4b4(Te6edf3fromNumTb4b4b4)Tffd700}Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff key=Tffd700$Te6edf3keyStrTa5d6ff canonical=Tffd700$Te6edf3canonicalNumTa5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff decision=Tffd700${Te6edf3transitionTb4b4b4.Te6edf3decisionTffd700}Ta5d6ff notify=Tffd700${Te6edf3transitionTb4b4b4.Te6edf3notifyNode Tff7b72!Tff7b72= Tff7b72nullTffd700}Ta5d6ff"
Tb4b4b4}
Te6edf3applyReceivedUserEffectsTb4b4b4(Te6edf3transitionTb4b4b4, Te6edf3sessionTb4b4b4)
Tff7b72return
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffhandleReceivedPositionTb4b4b4(
Te6edf3fromNumTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3myNodeNumTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3pTb4b4b4: Te6edf3ProtoPositionTb4b4b4,
Te6edf3defaultTimeTb4b4b4: Tffa657LongTb4b4b4,
Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContext?Tb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3isZeroPos Tff7b72= Tb4b4b4(Te6edf3pTb4b4b4.Te6edf3latitude_i Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72=Tff7b72= T79c0ff0 Tff7b72&Tff7b72& Tb4b4b4(Te6edf3pTb4b4b4.Te6edf3longitude_i Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72=Tff7b72= T79c0ff0
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffComplexConditionTa5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3myNodeNum Tff7b72=Tff7b72= Te6edf3fromNum Tff7b72&Tff7b72& Te6edf3isZeroPos Tff7b72&Tff7b72& Te6edf3pTb4b4b4.Te6edf3sats_in_view Tff7b72=Tff7b72= T79c0ff0 Tff7b72&Tff7b72& Te6edf3pTb4b4b4.Te6edf3time Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffIgnoring empty position update for the local nodeTa5d6ff" Tb4b4b4}
Tff7b72return
Tb4b4b4}
Te6edf3updateNodeAndSchedulePersistenceTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4, Te6edf3session Tff7b72= Te6edf3sessionTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
Tff7b72val Te6edf3rawPosTime Tff7b72= Tff7b72if Tb4b4b4(Te6edf3pTb4b4b4.Te6edf3time Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Te6edf3pTb4b4b4.Te6edf3time Tff7b72else Tb4b4b4(Te6edf3defaultTime Tff7b72/ Te6edf3TIME_MS_TO_STb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3posTime Tff7b72= Te6edf3clampTimestampToNowTb4b4b4(Te6edf3rawPosTimeTb4b4b4)
Tff7b72val Te6edf3newLastHeard Tff7b72= Te6edf3maxOfTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3lastHeardTb4b4b4, Te6edf3posTimeTb4b4b4)
Tff7b72val Te6edf3newPos Tff7b72=
Tff7b72when Tb4b4b4{
Te6edf3isZeroPos Tff7b72-Tff7b72>
Te6edf3pTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3time Tff7b72= Te6edf3posTimeTb4b4b4,
Te6edf3latitude_i Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4,
Te6edf3longitude_i Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4,
Te6edf3altitude Tff7b72= Te6edf3pTb4b4b4.Te6edf3altitude Tff7b72?: Te6edf3nodeTb4b4b4.Te6edf3positionTb4b4b4.Te6edf3altitudeTb4b4b4,
Te6edf3sats_in_view Tff7b72= Te6edf3pTb4b4b4.Te6edf3sats_in_viewTb4b4b4,
Tb4b4b4)
Tff7b72else Tff7b72-Tff7b72> Te6edf3preservingKnownPrecisionTb4b4b4(Te6edf3pTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3time Tff7b72= Te6edf3posTimeTb4b4b4)Tb4b4b4, Te6edf3nodeTb4b4b4.Te6edf3positionTb4b4b4)
Tb4b4b4}
Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3position Tff7b72= Te6edf3newPosTb4b4b4, Te6edf3lastHeard Tff7b72= Te6edf3newLastHeardTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffhandleReceivedTelemetryTb4b4b4(Te6edf3fromNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3telemetryTb4b4b4: Te6edf3TelemetryTb4b4b4) Tb4b4b4{
Te6edf3updateNodeTb4b4b4(Te6edf3fromNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
Tff7b72var Te6edf3nextNode Tff7b72= Te6edf3node
Te6edf3telemetryTb4b4b4.Te6edf3device_metricsTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3nextNode Tff7b72= Te6edf3nextNodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3deviceMetrics Tff7b72= Tffa657itTb4b4b4) Tb4b4b4}
Te6edf3telemetryTb4b4b4.Te6edf3environment_metricsTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3nextNode Tff7b72= Te6edf3nextNodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3environmentMetrics Tff7b72= Tffa657itTb4b4b4) Tb4b4b4}
Te6edf3telemetryTb4b4b4.Te6edf3power_metricsTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3nextNode Tff7b72= Te6edf3nextNodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3powerMetrics Tff7b72= Tffa657itTb4b4b4) Tb4b4b4}
Te6edf3telemetryTb4b4b4.Te6edf3air_quality_metricsTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3nextNode Tff7b72= Te6edf3nextNodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3airQualityMetrics Tff7b72= Tffa657itTb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3telemetryTime Tff7b72= Tff7b72if Tb4b4b4(Te6edf3telemetryTb4b4b4.Te6edf3time Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Te6edf3telemetryTb4b4b4.Te6edf3time Tff7b72else Te6edf3nodeTb4b4b4.Te6edf3lastHeard
Tff7b72val Te6edf3newLastHeard Tff7b72= Te6edf3clampTimestampToNowTb4b4b4(Te6edf3maxOfTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3lastHeardTb4b4b4, Te6edf3telemetryTimeTb4b4b4)Tb4b4b4)
Te6edf3nextNodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3lastHeard Tff7b72= Te6edf3newLastHeardTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffhandleReceivedPaxcounterTb4b4b4(Te6edf3fromNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3pTb4b4b4: Te6edf3PaxcountTb4b4b4, Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContext?Tb4b4b4) Tb4b4b4{
Te6edf3updateNodeAndSchedulePersistenceTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4, Te6edf3session Tff7b72= Te6edf3sessionTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3paxcounter Tff7b72= Te6edf3pTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffhandleReceivedNodeStatusTb4b4b4(Te6edf3fromNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3sTb4b4b4: Te6edf3StatusMessageTb4b4b4, Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContext?Tb4b4b4) Tb4b4b4{
Te6edf3updateNodeAndSchedulePersistenceTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4, Te6edf3session Tff7b72= Te6edf3sessionTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
Te6edf3applyNodeStatusTb4b4b4(Te6edf3nodeTb4b4b4, Te6edf3sTb4b4b4.Te6edf3statusTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffupdateNodeStatusTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3statusTb4b4b4: Tffa657String?Tb4b4b4) Tb4b4b4{
Te6edf3updateNodeTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3applyNodeStatusTb4b4b4(Te6edf3nodeTb4b4b4, Te6edf3statusTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffupdateNodeStatusAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3statusTb4b4b4: Tffa657String?Tb4b4b4) Tb4b4b4{
Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3nodeNumTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3applyNodeStatusTb4b4b4(Te6edf3nodeTb4b4b4, Te6edf3statusTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffapplyNodeStatusTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeTb4b4b4, Te6edf3statusTb4b4b4: Tffa657String?Tb4b4b4)Tb4b4b4: Te6edf3Node Tff7b72=
Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3nodeStatus Tff7b72= Te6edf3statusTff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffinstallNodeInfoTb4b4b4(Te6edf3infoTb4b4b4: Te6edf3ProtoNodeInfoTb4b4b4) Tb4b4b4{
T8b949e// Stage-2 configuration installation persists the complete node snapshot through installConfig.
Te6edf3updateNodeStateTb4b4b4(Te6edf3infoTb4b4b4.Te6edf3numTb4b4b4, Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3applyNodeInfoTb4b4b4(Te6edf3nodeTb4b4b4, Te6edf3infoTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffinstallNodeInfoAndPersistTb4b4b4(Te6edf3infoTb4b4b4: Te6edf3ProtoNodeInfoTb4b4b4) Tb4b4b4{
Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3infoTb4b4b4.Te6edf3numTb4b4b4) Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3applyNodeInfoTb4b4b4(Te6edf3nodeTb4b4b4, Te6edf3infoTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffapplyNodeInfoTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeTb4b4b4, Te6edf3infoTb4b4b4: Te6edf3ProtoNodeInfoTb4b4b4)Tb4b4b4: Te6edf3Node Tb4b4b4{
Tff7b72var Te6edf3next Tff7b72= Te6edf3node
Tff7b72val Te6edf3user Tff7b72= Te6edf3infoTb4b4b4.Te6edf3user
Tff7b72if Tb4b4b4(Te6edf3user Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Tff7b72!Te6edf3shouldPreserveExistingUserTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4, Te6edf3userTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72var Te6edf3newUser Tff7b72= Te6edf3userTb4b4b4.Te6edf3let Tb4b4b4{ Tff7b72if Tb4b4b4(Tffa657itTb4b4b4.Te6edf3is_licensed Tff7b72=Tff7b72= Tff7b72trueTb4b4b4) Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3public_key Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4) Tff7b72else Tffa657it Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3infoTb4b4b4.Te6edf3via_mqtt Tff7b72&Tff7b72& Tff7b72!Te6edf3newUserTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3endsWithTb4b4b4(Ta5d6ff"Ta5d6ff (MQTT)Ta5d6ff"Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3newUser Tff7b72= Te6edf3newUserTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3long_name Tff7b72= Ta5d6ff"Tffd700${Te6edf3newUserTb4b4b4.Te6edf3long_nameTffd700}Ta5d6ff (MQTT)Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Te6edf3next Tff7b72= Te6edf3nextTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3user Tff7b72= Te6edf3newUserTb4b4b4, Te6edf3publicKey Tff7b72= Te6edf3newUserTb4b4b4.Te6edf3public_keyTb4b4b4)
Tb4b4b4}
Te6edf3infoTb4b4b4.Te6edf3positionTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3position Tff7b72-Tff7b72>
T8b949e// The NodeDB snapshot from the connected device carries the coarsened position for nodes on a
T8b949e// precision-limited channel; do not let it clobber a more precise coordinate we already hold (#6360).
Tff7b72val Te6edf3timed Tff7b72= Te6edf3positionTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3time Tff7b72= Te6edf3clampTimestampToNowTb4b4b4(Te6edf3positionTb4b4b4.Te6edf3timeTb4b4b4)Tb4b4b4)
Te6edf3next Tff7b72= Te6edf3nextTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3position Tff7b72= Te6edf3preservingKnownPrecisionTb4b4b4(Te6edf3timedTb4b4b4, Te6edf3nextTb4b4b4.Te6edf3positionTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3nextTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3lastHeard Tff7b72= Te6edf3clampTimestampToNowTb4b4b4(Te6edf3infoTb4b4b4.Te6edf3last_heardTb4b4b4)Tb4b4b4,
Te6edf3deviceMetrics Tff7b72= Te6edf3infoTb4b4b4.Te6edf3device_metrics Tff7b72?: Te6edf3nextTb4b4b4.Te6edf3deviceMetricsTb4b4b4,
Te6edf3channel Tff7b72= Te6edf3infoTb4b4b4.Te6edf3channelTb4b4b4,
Te6edf3viaMqtt Tff7b72= Te6edf3infoTb4b4b4.Te6edf3via_mqttTb4b4b4,
Te6edf3hopsAway Tff7b72= Te6edf3infoTb4b4b4.Te6edf3hops_away Tff7b72?: Tff7b72-T79c0ff1Tb4b4b4,
Te6edf3isFavorite Tff7b72= Te6edf3infoTb4b4b4.Te6edf3is_favoriteTb4b4b4,
Te6edf3isIgnored Tff7b72= Te6edf3infoTb4b4b4.Te6edf3is_ignoredTb4b4b4,
Te6edf3isMuted Tff7b72= Te6edf3infoTb4b4b4.Te6edf3is_mutedTb4b4b4,
Te6edf3signsPackets Tff7b72= Te6edf3infoTb4b4b4.Te6edf3has_xeddsa_signedTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffinsertMetadataTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3metadataTb4b4b4: Te6edf3DeviceMetadataTb4b4b4, Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContext?Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3isRetiredAbsentTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4) Tff7b72return
Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3launchSessionWorkTb4b4b4(Te6edf3scopeTb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3isRetiredAbsentTb4b4b4(Te6edf3nodeNumTb4b4b4)Tb4b4b4) Te6edf3nodeRepositoryTb4b4b4.Te6edf3insertMetadataTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3metadataTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffshouldPreserveExistingUserTb4b4b4(Te6edf3existingTb4b4b4: Te6edf3UserTb4b4b4, Te6edf3incomingTb4b4b4: Te6edf3UserTb4b4b4)Tb4b4b4: Tffa657Boolean Tb4b4b4{
Tff7b72val Te6edf3isDefaultName Tff7b72= Te6edf3incomingTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3DEFAULT_NODE_NAME_REGEXTb4b4b4)
Tff7b72val Te6edf3isDefaultHwModel Tff7b72= Te6edf3incomingTb4b4b4.Te6edf3hw_model Tff7b72=Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSET
Tff7b72val Te6edf3hasCustomIdentity Tff7b72=
Te6edf3existingTb4b4b4.Te6edf3id Tff7b72!Tff7b72= Te6edf3incomingTb4b4b4.Te6edf3id Tff7b72|Tff7b72|
Te6edf3existingTb4b4b4.Te6edf3long_name Tff7b72!Tff7b72= Te6edf3incomingTb4b4b4.Te6edf3long_name Tff7b72|Tff7b72|
Te6edf3existingTb4b4b4.Te6edf3short_name Tff7b72!Tff7b72= Te6edf3incomingTb4b4b4.Te6edf3short_name
Tff7b72val Te6edf3hasExistingUser Tff7b72=
Te6edf3existingTb4b4b4.Te6edf3idTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72& Tb4b4b4(Te6edf3existingTb4b4b4.Te6edf3hw_model Tff7b72!Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSET Tff7b72|Tff7b72| Te6edf3hasCustomIdentityTb4b4b4)
Tff7b72return Te6edf3hasExistingUser Tff7b72&Tff7b72& Te6edf3isDefaultName Tff7b72&Tff7b72& Te6edf3isDefaultHwModel
Tb4b4b4}
T8b949e/**
* Classification of one received-user reduction. Returned by [reduceReceivedUser] so callers can emit a single
* identity-routing log line per committed transition.
*/
Tff7b72private Tff7b72enum Tff7b72class T56d364ReceivedUserDecision Tb4b4b4{
Te6edf3LOCAL_UPDATETb4b4b4,
Te6edf3KNOWN_SAME_NUMBERTb4b4b4,
Te6edf3GENUINE_NEW_NODETb4b4b4,
Te6edf3RETIRED_NO_KEY_SUPPRESSEDTb4b4b4,
Te6edf3RETIRED_WITHOUT_HINT_SUPPRESSEDTb4b4b4,
Te6edf3RETIRED_SAME_KEY_SUPPRESSEDTb4b4b4,
Te6edf3RETIRED_KEY_ALREADY_REPRESENTEDTb4b4b4,
Te6edf3RETIRED_DIFFERENT_KEY_REACTIVATEDTb4b4b4,
Te6edf3CANONICAL_DUPLICATE_RECONCILEDTb4b4b4,
Te6edf3STALE_PRESENTATION_REMOVEDTb4b4b4,
Te6edf3AMBIGUOUS_DUPLICATE_UPDATEDTb4b4b4,
Te6edf3IDENTITY_CONFLICT_IGNOREDTb4b4b4,
Tb4b4b4}
T8b949e/**
* Result of classifying one [User] packet against an in-memory [NodeIndex] snapshot. The reducer is pure: it only
* reads [before] and the packet fields, and produces the next index plus the side effects to apply once the CAS
* commits. Safe to re-evaluate on every retry.
*/
Tff7b72private Tff7b72data Tff7b72class T56d364ReceivedUserTransitionTb4b4b4(
Tff7b72val Te6edf3afterTb4b4b4: Te6edf3NodeIndexTb4b4b4,
T8b949e/** Ordinary same-number upsert to apply after CAS; duplicate correlation never supplies this value. */
Tff7b72val Te6edf3upsertNodeTb4b4b4: Te6edf3Node?Tb4b4b4,
T8b949e/** Node to fire a "new node seen" notification for (null if not a genuine new node). */
Tff7b72val Te6edf3notifyNodeTb4b4b4: Te6edf3Node?Tb4b4b4,
T8b949e/** Retired number to reactivate after a validated, genuinely new identity claims the vacant slot. */
Tff7b72val Te6edf3unretireNodeNumTb4b4b4: Tffa657Int? Tff7b72= Tff7b72nullTb4b4b4,
Tff7b72val Te6edf3decisionTb4b4b4: Te6edf3ReceivedUserDecisionTb4b4b4,
Tb4b4b4)
T8b949e/**
* Pure reducer that classifies an incoming [User] packet against the [before] snapshot and returns the next index
* plus queued side effects. No logging, no DB calls, no coroutine launches — deterministic and safe to re-run on
* CAS retry.
*
* A validated public key is only an in-memory correlation hint. It can choose/suppress duplicate presentations but
* cannot authorize repository deletion, metadata removal, DM remapping, or durable node-number migration. Those
* operations remain exclusively in the trusted config-install DAO path.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffLongMethodTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffreduceReceivedUserTb4b4b4(
Te6edf3beforeTb4b4b4: Te6edf3NodeIndexTb4b4b4,
Te6edf3fromNumTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3pTb4b4b4: Te6edf3UserTb4b4b4,
Te6edf3channelTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3manuallyVerifiedTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3myNumTb4b4b4: Tffa657Int?Tb4b4b4,
Te6edf3retiredAbsentTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3retiredKeyHintTb4b4b4: Te6edf3ByteString? Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3ReceivedUserTransition Tb4b4b4{
Tff7b72val Te6edf3resolvedKey Tff7b72= Te6edf3resolveValidatedPublicKeyHintTb4b4b4(Te6edf3pTb4b4b4.Te6edf3public_keyTb4b4b4)
Tff7b72val Te6edf3existing Tff7b72= Te6edf3beforeTb4b4b4.Te6edf3byNumTff7b72[Te6edf3fromNumTff7b72]
Tff7b72if Tb4b4b4(Te6edf3retiredAbsentTb4b4b4) Tb4b4b4{
T8b949e// Suppress same-key replay even when canonical representative hasn't yet appeared in
T8b949e// the in-memory index. The retiredKeyHint was captured from the node as it was retired.
Tff7b72if Tb4b4b4(Te6edf3resolvedKey Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3retiredKeyHint Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3resolvedKey Tff7b72=Tff7b72= Te6edf3retiredKeyHintTb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3beforeTb4b4b4,
Te6edf3upsertNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3notifyNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3RETIRED_SAME_KEY_SUPPRESSEDTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// A packet with no valid key is suppressed for a retired number.
Tff7b72if Tb4b4b4(Te6edf3resolvedKey Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3beforeTb4b4b4,
Te6edf3upsertNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3notifyNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3RETIRED_NO_KEY_SUPPRESSEDTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// A retired slot without a trusted old-key hint has no evidence that a different-looking packet is a
T8b949e// legitimate reuse rather than the stale identity. Keep it retired for this process session.
Tff7b72if Tb4b4b4(Te6edf3retiredKeyHint Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3beforeTb4b4b4,
Te6edf3upsertNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3notifyNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3RETIRED_WITHOUT_HINT_SUPPRESSEDTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// Key is already represented elsewhere — stale replay, suppress.
Tff7b72val Te6edf3keyAlreadyRepresented Tff7b72=
Te6edf3resolvedKeyTb4b4b4.Te6edf3let Tb4b4b4{ Te6edf3key Tff7b72-Tff7b72> Te6edf3beforeTb4b4b4.Te6edf3candidateNumsByPublicKeyTff7b72[Te6edf3keyTff7b72]Tb4b4b4.Te6edf3orEmptyTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3keyAlreadyRepresentedTb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3beforeTb4b4b4,
Te6edf3upsertNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3notifyNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3RETIRED_KEY_ALREADY_REPRESENTEDTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// Different valid, unrepresented key — legitimate number reuse.
Tff7b72return Te6edf3sameNumberUserTransitionTb4b4b4(
Te6edf3before Tff7b72= Te6edf3beforeTb4b4b4,
Te6edf3fromNum Tff7b72= Te6edf3fromNumTb4b4b4,
Te6edf3existing Tff7b72= Te6edf3createDefaultNodeTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3channelTb4b4b4)Tb4b4b4,
Te6edf3incoming Tff7b72= Te6edf3pTb4b4b4,
Te6edf3channel Tff7b72= Te6edf3channelTb4b4b4,
Te6edf3manuallyVerified Tff7b72= Te6edf3manuallyVerifiedTb4b4b4,
Te6edf3persist Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3allowNotification Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3unretireNodeNum Tff7b72= Te6edf3fromNumTb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3copyTb4b4b4(Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3RETIRED_DIFFERENT_KEY_REACTIVATEDTb4b4b4)
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3fromNum Tff7b72=Tff7b72= Te6edf3myNumTb4b4b4) Tb4b4b4{
T8b949e// Local-link data may persist an ordinary update at its current number. Duplicate cleanup is still
T8b949e// in-memory only; trusted installConfig migration owns durable renumbering and app-local state transfer.
Tff7b72val Te6edf3otherSameKeyNums Tff7b72=
Te6edf3resolvedKey
Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3key Tff7b72-Tff7b72> Te6edf3beforeTb4b4b4.Te6edf3candidateNumsByPublicKeyTff7b72[Te6edf3keyTff7b72]Tb4b4b4.Te6edf3orEmptyTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657it Tff7b72!Tff7b72= Te6edf3fromNum Tb4b4b4} Tb4b4b4}
Tb4b4b4.Te6edf3orEmptyTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3afterRemovals Tff7b72= Te6edf3otherSameKeyNumsTb4b4b4.Te6edf3foldTb4b4b4(Te6edf3beforeTb4b4b4) Tb4b4b4{ Te6edf3idxTb4b4b4, Te6edf3num Tff7b72-Tff7b72> Te6edf3idxTb4b4b4.Te6edf3removeTb4b4b4(Te6edf3numTb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3localNode Tff7b72= Te6edf3afterRemovalsTb4b4b4.Te6edf3byNumTff7b72[Te6edf3fromNumTff7b72] Tff7b72?: Te6edf3createDefaultNodeTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3channelTb4b4b4)
Tff7b72val Te6edf3transformed Tff7b72= Te6edf3transformUserNodeTb4b4b4(Te6edf3localNodeTb4b4b4, Te6edf3pTb4b4b4, Te6edf3channelTb4b4b4, Te6edf3manuallyVerifiedTb4b4b4)
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3afterRemovalsTb4b4b4.Te6edf3putTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3transformedTb4b4b4, Te6edf3preferredNum Tff7b72= Te6edf3fromNumTb4b4b4)Tb4b4b4,
Te6edf3upsertNode Tff7b72= Te6edf3transformedTb4b4b4,
Te6edf3notifyNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3LOCAL_UPDATETb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3resolvedKey Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3canonicalNum Tff7b72= Te6edf3resolvedKeyTb4b4b4.Te6edf3crc32Tb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3existingKey Tff7b72= Te6edf3existingTff7b72?.Te6edf3letTb4b4b4(Tff7b72::Te6edf3resolveNodePublicKeyHintTb4b4b4)
Tff7b72val Te6edf3otherSameKeyNums Tff7b72= Te6edf3beforeTb4b4b4.Te6edf3candidateNumsByPublicKeyTff7b72[Te6edf3resolvedKeyTff7b72]Tb4b4b4.Te6edf3orEmptyTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657it Tff7b72!Tff7b72= Te6edf3fromNum Tb4b4b4}
T8b949e// Fast path: a known same-number presentation needs no candidate reconciliation when it is already at the
T8b949e// canonical number or has no duplicate. A canonical duplicate remains in-memory-only because an ordinary
T8b949e// DAO upsert could remap the row through same-key verification.
Tff7b72if Tb4b4b4(Te6edf3existingKey Tff7b72=Tff7b72= Te6edf3resolvedKey Tff7b72&Tff7b72& Tb4b4b4(Te6edf3fromNum Tff7b72=Tff7b72= Te6edf3canonicalNum Tff7b72|Tff7b72| Te6edf3otherSameKeyNumsTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3sameNumberUserTransitionTb4b4b4(
Te6edf3beforeTb4b4b4,
Te6edf3fromNumTb4b4b4,
Te6edf3checkNotNullTb4b4b4(Te6edf3existingTb4b4b4)Tb4b4b4,
Te6edf3pTb4b4b4,
Te6edf3channelTb4b4b4,
Te6edf3manuallyVerifiedTb4b4b4,
Te6edf3persist Tff7b72= Te6edf3otherSameKeyNumsTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3allowNotification Tff7b72= Tff7b72falseTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3fromNum Tff7b72=Tff7b72= Te6edf3canonicalNum Tff7b72&Tff7b72& Te6edf3otherSameKeyNumsTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3slotAcceptsCanonical Tff7b72=
Te6edf3existing Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3existingKey Tff7b72=Tff7b72= Te6edf3resolvedKey Tff7b72|Tff7b72| Te6edf3NodeIndexTb4b4b4.Te6edf3isDefaultIdentityPlaceholderTb4b4b4(Te6edf3existingTb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3slotAcceptsCanonicalTb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3beforeTb4b4b4,
Te6edf3upsertNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3notifyNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3IDENTITY_CONFLICT_IGNOREDTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// Canonical presentation may replace its own default placeholder while preserving all non-user fields.
T8b949e// Same-key noncanonical representatives are removed from memory only.
Tff7b72val Te6edf3afterRemovals Tff7b72= Te6edf3otherSameKeyNumsTb4b4b4.Te6edf3foldTb4b4b4(Te6edf3beforeTb4b4b4) Tb4b4b4{ Te6edf3indexTb4b4b4, Te6edf3num Tff7b72-Tff7b72> Te6edf3indexTb4b4b4.Te6edf3removeTb4b4b4(Te6edf3numTb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3baseNode Tff7b72= Te6edf3afterRemovalsTb4b4b4.Te6edf3byNumTff7b72[Te6edf3fromNumTff7b72] Tff7b72?: Te6edf3createDefaultNodeTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3channelTb4b4b4)
Tff7b72val Te6edf3transformed Tff7b72=
Te6edf3transformUserNodeTb4b4b4(Te6edf3baseNodeTb4b4b4, Te6edf3pTb4b4b4, Te6edf3channelTb4b4b4, Te6edf3manuallyVerifiedTb4b4b4)Tb4b4b4.Te6edf3let Tb4b4b4{ Te6edf3updated Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3existing Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3NodeIndexTb4b4b4.Te6edf3isDefaultIdentityPlaceholderTb4b4b4(Te6edf3existingTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3updatedTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3channel Tff7b72= Te6edf3existingTb4b4b4.Te6edf3channelTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3updated
Tb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3afterRemovalsTb4b4b4.Te6edf3putTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3transformedTb4b4b4, Te6edf3preferredNum Tff7b72= Te6edf3fromNumTb4b4b4)Tb4b4b4,
Te6edf3upsertNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3notifyNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3CANONICAL_DUPLICATE_RECONCILEDTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3canonicalNum Tff7b72in Te6edf3otherSameKeyNumsTb4b4b4) Tb4b4b4{
T8b949e// A stale remote presentation yields only when its own slot already carries the same validated hint.
T8b949e// An absent slot, no-key telemetry/position placeholder, or different identity is preserved.
Tff7b72val Te6edf3after Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3existingKey Tff7b72=Tff7b72= Te6edf3resolvedKeyTb4b4b4) Te6edf3beforeTb4b4b4.Te6edf3removeTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3preferredNum Tff7b72= Te6edf3canonicalNumTb4b4b4) Tff7b72else Te6edf3before
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3afterTb4b4b4,
Te6edf3upsertNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3notifyNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3STALE_PRESENTATION_REMOVEDTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3otherSameKeyNumsTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
T8b949e// Direction is ambiguous among noncanonical presentations. Accept an empty/default fromNum slot so a
T8b949e// legitimate pre-2.8 or fixed-number peer can move between noncanonical numbers without becoming
T8b949e// locked out behind a telemetry/position placeholder. Preserve established different-key identities.
Tff7b72val Te6edf3slotAcceptsAmbiguous Tff7b72=
Te6edf3existing Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3existingKey Tff7b72=Tff7b72= Te6edf3resolvedKey Tff7b72|Tff7b72| Te6edf3NodeIndexTb4b4b4.Te6edf3isDefaultIdentityPlaceholderTb4b4b4(Te6edf3existingTb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3slotAcceptsAmbiguousTb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3beforeTb4b4b4,
Te6edf3upsertNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3notifyNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3IDENTITY_CONFLICT_IGNOREDTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e// Public keys are broadcast, unauthenticated correlation hints. Installing the presentation here is
T8b949e// deliberately in-memory-only: it must never authorize persistence, migration, or deletion. Trusted
T8b949e// config installation remains the sole authority for durable identity changes.
Tff7b72val Te6edf3baseNode Tff7b72= Te6edf3existing Tff7b72?: Te6edf3createDefaultNodeTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3channelTb4b4b4)
Tff7b72val Te6edf3transformed Tff7b72= Te6edf3transformUserNodeTb4b4b4(Te6edf3baseNodeTb4b4b4, Te6edf3pTb4b4b4, Te6edf3channelTb4b4b4, Te6edf3manuallyVerifiedTb4b4b4)
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3beforeTb4b4b4.Te6edf3putTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3transformedTb4b4b4, Te6edf3preferredNum Tff7b72= Te6edf3fromNumTb4b4b4)Tb4b4b4,
Te6edf3upsertNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3notifyNode Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3AMBIGUOUS_DUPLICATE_UPDATEDTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3sameNumberUserTransitionTb4b4b4(
Te6edf3beforeTb4b4b4,
Te6edf3fromNumTb4b4b4,
Te6edf3existing Tff7b72?: Te6edf3createDefaultNodeTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3channelTb4b4b4)Tb4b4b4,
Te6edf3pTb4b4b4,
Te6edf3channelTb4b4b4,
Te6edf3manuallyVerifiedTb4b4b4,
Te6edf3persist Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3allowNotification Tff7b72= Tff7b72trueTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffsameNumberUserTransitionTb4b4b4(
Te6edf3beforeTb4b4b4: Te6edf3NodeIndexTb4b4b4,
Te6edf3fromNumTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3existingTb4b4b4: Te6edf3NodeTb4b4b4,
Te6edf3incomingTb4b4b4: Te6edf3UserTb4b4b4,
Te6edf3channelTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3manuallyVerifiedTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3persistTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3allowNotificationTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3unretireNodeNumTb4b4b4: Tffa657Int? Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3ReceivedUserTransition Tb4b4b4{
Tff7b72val Te6edf3isNewNode Tff7b72= Te6edf3existingTb4b4b4.Te6edf3isUnknownUser Tff7b72&Tff7b72& Te6edf3incomingTb4b4b4.Te6edf3hw_model Tff7b72!Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSET
Tff7b72val Te6edf3shouldPreserve Tff7b72= Te6edf3shouldPreserveExistingUserTb4b4b4(Te6edf3existingTb4b4b4.Te6edf3userTb4b4b4, Te6edf3incomingTb4b4b4)
Tff7b72val Te6edf3transformed Tff7b72= Te6edf3transformUserNodeTb4b4b4(Te6edf3existingTb4b4b4, Te6edf3incomingTb4b4b4, Te6edf3channelTb4b4b4, Te6edf3manuallyVerifiedTb4b4b4)
Tff7b72val Te6edf3notify Tff7b72= Tff7b72if Tb4b4b4(Te6edf3allowNotification Tff7b72&Tff7b72& Te6edf3isNewNode Tff7b72&Tff7b72& Tff7b72!Te6edf3shouldPreserveTb4b4b4) Te6edf3transformed Tff7b72else Tff7b72null
Tff7b72val Te6edf3decision Tff7b72=
Tff7b72when Tb4b4b4{
Te6edf3allowNotification Tff7b72&Tff7b72& Te6edf3isNewNode Tff7b72&Tff7b72& Tff7b72!Te6edf3shouldPreserve Tff7b72-Tff7b72> Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3GENUINE_NEW_NODE
Tff7b72else Tff7b72-Tff7b72> Te6edf3ReceivedUserDecisionTb4b4b4.Te6edf3KNOWN_SAME_NUMBER
Tb4b4b4}
Tff7b72return Te6edf3ReceivedUserTransitionTb4b4b4(
Te6edf3after Tff7b72= Te6edf3beforeTb4b4b4.Te6edf3putTb4b4b4(Te6edf3fromNumTb4b4b4, Te6edf3transformedTb4b4b4)Tb4b4b4,
Te6edf3upsertNode Tff7b72= Te6edf3transformedTb4b4b4.Te6edf3takeIf Tb4b4b4{ Te6edf3persist Tb4b4b4}Tb4b4b4,
Te6edf3notifyNode Tff7b72= Te6edf3notifyTb4b4b4,
Te6edf3unretireNodeNum Tff7b72= Te6edf3unretireNodeNumTb4b4b4,
Te6edf3decision Tff7b72= Te6edf3decisionTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e/** Pure factory for a placeholder [Node] at [num]. Kept side-effect-free so the reducer can call it safely. */
Tff7b72private Tff7b72fun Td2a8ffcreateDefaultNodeTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4, Te6edf3channelTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3Node Tb4b4b4{
Tff7b72val Te6edf3userId Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3numTb4b4b4)
Tff7b72return Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3numTb4b4b4,
Te6edf3user Tff7b72=
Te6edf3UserTb4b4b4(
Te6edf3id Tff7b72= Te6edf3userIdTb4b4b4,
Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffMeshtastic Tffd700${Te6edf3userIdTb4b4b4.Te6edf3takeLastTb4b4b4(Te6edf3GENERATED_NODE_NAME_SUFFIX_LENGTHTb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Te6edf3short_name Tff7b72= Te6edf3userIdTb4b4b4.Te6edf3takeLastTb4b4b4(Te6edf3GENERATED_NODE_NAME_SUFFIX_LENGTHTb4b4b4)Tb4b4b4,
Te6edf3hw_model Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3channel Tff7b72= Te6edf3channelTb4b4b4,
Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Pure transform of an existing [node] under an incoming [User] packet (extracted from the legacy updateNode path).
*/
Tff7b72private Tff7b72fun Td2a8fftransformUserNodeTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeTb4b4b4, Te6edf3pTb4b4b4: Te6edf3UserTb4b4b4, Te6edf3channelTb4b4b4: Tffa657IntTb4b4b4, Te6edf3manuallyVerifiedTb4b4b4: Tffa657BooleanTb4b4b4)Tb4b4b4: Te6edf3Node Tb4b4b4{
Tff7b72val Te6edf3shouldPreserve Tff7b72= Te6edf3shouldPreserveExistingUserTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4, Te6edf3pTb4b4b4)
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3shouldPreserveTb4b4b4) Tb4b4b4{
Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3channel Tff7b72= Te6edf3channelTb4b4b4, Te6edf3manuallyVerified Tff7b72= Te6edf3manuallyVerifiedTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72val Te6edf3incomingKey Tff7b72= Te6edf3resolveValidatedPublicKeyHintTb4b4b4(Te6edf3pTb4b4b4.Te6edf3public_keyTb4b4b4)
Tff7b72val Te6edf3sanitizedUser Tff7b72= Tff7b72if Tb4b4b4(Te6edf3incomingKey Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Te6edf3pTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3public_key Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4) Tff7b72else Te6edf3p
T8b949e// Prefer node.publicKey when valid (the authoritative stored key); fall back to node.user.public_key.
Tff7b72val Te6edf3existingKey Tff7b72= Te6edf3resolveNodePublicKeyHintTb4b4b4(Te6edf3nodeTb4b4b4)
Tff7b72val Te6edf3keyMatch Tff7b72= Te6edf3existingKey Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3existingKey Tff7b72=Tff7b72= Te6edf3incomingKey
Tff7b72val Te6edf3newUser Tff7b72= Tff7b72if Tb4b4b4(Te6edf3keyMatchTb4b4b4) Te6edf3sanitizedUser Tff7b72else Te6edf3sanitizedUserTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3public_key Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)
Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3user Tff7b72= Te6edf3newUserTb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3newUserTb4b4b4.Te6edf3public_keyTb4b4b4,
Te6edf3channel Tff7b72= Te6edf3channelTb4b4b4,
Te6edf3manuallyVerified Tff7b72= Te6edf3manuallyVerifiedTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/** Applies ordinary same-number persistence and notification side effects once after the reducer CAS commits. */
Tff7b72private Tff7b72fun Td2a8ffapplyReceivedUserEffectsTb4b4b4(Te6edf3transitionTb4b4b4: Te6edf3ReceivedUserTransitionTb4b4b4, Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContext?Tb4b4b4) Tb4b4b4{
Te6edf3transitionTb4b4b4.Te6edf3upsertNodeTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3shouldPersistTb4b4b4(Te6edf3nodeTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3launchSessionWorkTb4b4b4(Te6edf3scopeTb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4{ Te6edf3persistLatestNodeTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3transitionTb4b4b4.Te6edf3notifyNodeTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3launchSessionWorkTb4b4b4(Te6edf3scopeTb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4{
T8b949e// Resolve the display title before validation so the suspending compose-resources call does
T8b949e// not create a window where state can change after identity checks pass.
Tff7b72val Te6edf3title Tff7b72= Te6edf3notificationTitleFormatterTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3short_nameTb4b4b4)
T8b949e// Revalidate immediately before dispatch: the number may have been retired, cleared, or
T8b949e// replaced by a different identity between the reducer CAS and this coroutine dispatch.
Tff7b72val Te6edf3currentState Tff7b72= Te6edf3nodeStateTb4b4b4.Te6edf3value
Tff7b72if Tb4b4b4(Te6edf3currentStateTb4b4b4.Te6edf3isRetiredAbsentTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ff[NodeIdentity] notification-skip num=Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff reason=retiredTa5d6ff" Tb4b4b4}
Tff7b72returnTf0883e@launchSessionWork
Tb4b4b4}
Tff7b72val Te6edf3currentNode Tff7b72= Te6edf3currentStateTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNumTff7b72[Te6edf3nodeTb4b4b4.Te6edf3numTff7b72]
Tff7b72if Tb4b4b4(Te6edf3currentNode Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ff[NodeIdentity] notification-skip num=Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff reason=absentTa5d6ff" Tb4b4b4}
Tff7b72returnTf0883e@launchSessionWork
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3currentStateTb4b4b4.Te6edf3localNodeNum Tff7b72=Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ff[NodeIdentity] notification-skip num=Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff reason=local-nodeTa5d6ff" Tb4b4b4}
Tff7b72returnTf0883e@launchSessionWork
Tb4b4b4}
T8b949e// Identity comparison: prefer validated public-key equality; fall back to user.id.
T8b949e// Reject if one snapshot has a key and the other does not — that is an identity change.
Tff7b72val Te6edf3nodeKey Tff7b72= Te6edf3resolveNodePublicKeyHintTb4b4b4(Te6edf3nodeTb4b4b4)
Tff7b72val Te6edf3currentKey Tff7b72= Te6edf3resolveNodePublicKeyHintTb4b4b4(Te6edf3currentNodeTb4b4b4)
Tff7b72val Te6edf3sameIdentity Tff7b72=
Tff7b72when Tb4b4b4{
Te6edf3nodeKey Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3currentKey Tff7b72!Tff7b72= Tff7b72null Tff7b72-Tff7b72> Te6edf3nodeKey Tff7b72=Tff7b72= Te6edf3currentKey
Te6edf3nodeKey Tff7b72=Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3currentKey Tff7b72=Tff7b72= Tff7b72null Tff7b72-Tff7b72>
Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72&
Te6edf3currentNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72&
Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3currentNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id
Tff7b72else Tff7b72-Tff7b72> Tff7b72false
Tb4b4b4}
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3sameIdentityTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ff[NodeIdentity] notification-skip num=Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff reason=identity-changedTa5d6ff" Tb4b4b4}
Tff7b72returnTf0883e@launchSessionWork
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ff[NodeIdentity] notification-dispatch num=Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff" Tb4b4b4}
Te6edf3notificationManagerTb4b4b4.Te6edf3dispatchTb4b4b4(
Te6edf3NotificationTb4b4b4(
Te6edf3title Tff7b72= Te6edf3titleTb4b4b4,
Te6edf3message Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4,
Te6edf3category Tff7b72= Te6edf3NotificationTb4b4b4.Te6edf3CategoryTb4b4b4.Te6edf3NodeEventTb4b4b4,
Te6edf3id Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4,
Te6edf3deepLinkUri Tff7b72= Ta5d6ff"Ta5d6ffmeshtastic://meshtastic/nodes/Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Test seam over the notification title; production resolves compose-resources lazily inside the dispatch
* coroutine.
*/
Tff7b72internal Tff7b72var Te6edf3notificationTitleFormatterTb4b4b4: Te6edf3suspend Tb4b4b4(Tffa657StringTb4b4b4) Tff7b72-Tff7b72> Tffa657String Tff7b72= Tb4b4b4{ Te6edf3shortName Tff7b72-Tff7b72>
Te6edf3getStringSuspendTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3new_node_seenTb4b4b4, Te6edf3shortNameTb4b4b4)
Tb4b4b4}
T8b949e/** Test seam invoked after reduction but before CAS to deterministically race a local-node-number update. */
Tff7b72internal Tff7b72var Te6edf3receivedUserReductionHookTb4b4b4: Tb4b4b4(Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tff7b72? Tff7b72= Tff7b72null
Tff7b72override Tff7b72fun Td2a8fftoNodeIDTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657String Tff7b72= Tff7b72if Tb4b4b4(Te6edf3nodeNum Tff7b72=Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3NODENUM_BROADCASTTb4b4b4) Tb4b4b4{
Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCAST
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3nodeStateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3indexTb4b4b4.Te6edf3byNumTff7b72[Te6edf3nodeNumTff7b72]Tff7b72?.Te6edf3userTff7b72?.Te6edf3id Tff7b72?: Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3nodeNumTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.41s